---
title: "Referencia del SDK"
description: "Toda la API de @beeuni/sdk: métodos del cliente, módulos, turnos y distribución."
---

Esta página recoge toda la superficie de la API de `@beeuni/sdk`: cada método del cliente, los
módulos independientes de frameworks, las utilidades de turnos y cómo se distribuye el paquete. Consulta
[SDK](/docs/es/sdk) para empezar y [Sesiones](/docs/es/sdk/sessions) para conocer a fondo el
ciclo de vida de las sesiones.

<a id="the-client"></a>

## El cliente

`createBeeuni(config)` devuelve un cliente. Cada método es una llamada tipada a
la API de la plataforma. Los manejadores `project(id)` y `session(pid, sid)` vinculan los identificadores
para que nunca tengas que repetirlos.

```ts
const beeuni = createBeeuni({ backendUrl, getToken });

beeuni.accounts; // account / team operations
beeuni.accountInvites; // invite accept/decline by token alone
beeuni.projects; // top-level project operations
beeuni.connectors; // Connector calls scoped by an agent-minted token
beeuni.connector(slug); // one connector in the token's scope → see p.connector(slug)
beeuni.project(id); // id-bound project handle
beeuni.session(pid, sid); // id-bound session handle → see Sessions
beeuni.github; // GitHub App install + repo linking (account-scoped)
beeuni.gitBackend; // the instance git backend ("Beeuni managed")
beeuni.billing; // credits, subscription, tier, transactions
beeuni.sandboxShares; // public share links for a sandbox port
beeuni.connectStatus; // easy-connect status
beeuni.marketplace; // public marketplace catalog
beeuni.validateToken; // pasted-API-key check
beeuni.config; // platform config in effect
beeuni.runtime(); // runtime REST compatibility client
```

<a id="accounts--beeuniaccounts"></a>

### Cuentas — `beeuni.accounts`

| método | función |
| --- | --- |
| `list()` · `get(accountId)` | cuentas a las que perteneces · una cuenta |
| `create({ name })` · `updateName(accountId, name)` | crear · renombrar una cuenta |
| `branding.get(accountId)` · `branding.update(accountId, { app_name })` · `branding.uploadAsset(accountId, kind, file)` · `branding.removeAsset(accountId, kind)` · `branding.reset(accountId)` | identidad de marca de la organización (Enterprise): logotipo / icono / favicon y nombre de producto propios; `kind` es `logo` · `icon` · `favicon`, o `logo_dark` · `icon_dark` · `favicon_dark` para la variante del esquema oscuro |
| `members(accountId)` · `invite(accountId, input)` | listar miembros · invitar a uno |
| `updateMemberRole(accountId, userId, role)` · `removeMember(accountId, userId)` | asignar el rol de la cuenta · eliminar un miembro |
| `invites(accountId)` | invitaciones pendientes |
| `cancelInvite(accountId, inviteId)` · `resendInvite(accountId, inviteId)` | cancelar · reenviar una invitación pendiente |
| `leave(accountId)` | abandonar la cuenta |

`accounts.tokens` genera claves de API con alcance de cuenta (`beeuni_pat_...`). Consulta
[Autenticación del SDK](/docs/es/accounts#tokens) para conocer el modelo completo de tokens.

| método | función |
| --- | --- |
| `tokens.list(accountId?, options?)` | listar claves de API — las de toda la cuenta, o solo las tuyas con `{ mine: true }` |
| `tokens.create(input)` | generar una — `{ accountId?, name, expiresAt?, projectId? }` |
| `tokens.revoke(tokenId, accountId?)` | revocar una |

`accounts.audit` es el registro de reconstrucción empresarial. Combina solicitudes
 autenticadas a la API con eventos semánticos de sesiones, conectores, aprobaciones y equipos.

| método | función |
| --- | --- |
| `audit.log(accountId, filters?)` | listar eventos por proyecto, sesión, actor, origen, resultado, solicitud, correlación, recurso, acción o tiempo |
| `audit.export(accountId, filters?)` | exportar el mismo flujo de eventos filtrado como CSV o JSONL |
| `audit.webhooks.list/create/update/remove(...)` | gestionar webhooks SIEM firmados para el flujo centralizado |

Cada evento incluye `project_id`, `session_id`, `actor_type`, `source`, `outcome`,
`request_id`, `trace_id` y `correlation_id` cuando la acción los proporciona.
La API no almacena cuerpos de solicitudes, prompts, secretos, credenciales ni argumentos
sin procesar de los conectores en el evento centralizado. Los eventos de conectores pueden incluir una
vista previa limitada de los argumentos que oculta campos con formato de credenciales y datos opacos.

<a id="access-assignments"></a>

### Asignaciones de acceso

Beeuni tiene un único registro de concesión: una **asignación**. Vincula una entidad principal (`user`,
`group`, `service_account` o `pending`) a un rol, en un ámbito (`account`,
o un `project`), opcionalmente limitado a un objeto (`agent`, `skill`, `secret`,
`app` o `trigger`) y opcionalmente con un `expires_at`. El acceso de grupos,
el acceso por recurso y las vinculaciones de roles personalizados son todos asignaciones. Consulta
[Cuentas y acceso](/docs/es/accounts#one-access-model) para conocer el modelo.

La superficie REST canónica es:

| Método + ruta | Función |
| --- | --- |
| `GET /v1/accounts/{accountId}/iam/assignments` | listar asignaciones, filtradas por entidad principal, ámbito, objeto o rol |
| `POST /v1/accounts/{accountId}/iam/assignments` | crear una asignación |
| `DELETE /v1/accounts/{accountId}/iam/assignments/{assignmentId}` | revocar una asignación |
| `GET /v1/accounts/{accountId}/iam/permissions` | el catálogo de permisos, como datos |
| `GET /v1/accounts/{accountId}/iam/roles` · `…/roles/{roleId}/permissions` | roles · permisos de un rol |

El SDK los expone como `listAssignments`, `createAssignment`,
`revokeAssignment` y `listPermissions`. Una fila del catálogo contiene `action`,
`scope_type`, `resource_type`, `delegable`, `description`, `area`, `level` y
`implies` — léela en lugar de codificar de forma fija las cadenas de acciones.

Asignar un rol personalizado requiere que la cuenta tenga el derecho `rbac`; sin él, la ruta responde
`402` con `code: "entitlement_required"`.

<a id="account-invites--beeuniaccountinvites"></a>

### Invitaciones a cuentas — `beeuni.accountInvites`

Se accede solo con el token de invitación — la persona invitada puede no ser miembro todavía.

| método | función |
| --- | --- |
| `listMine()` | todas las invitaciones pendientes y no caducadas dirigidas a tu correo electrónico, con el espacio de trabajo y los proyectos a los que dan acceso |
| `describe(inviteId)` · `accept(inviteId)` · `decline(inviteId)` | previsualizar · aceptar · rechazar una invitación |

<a id="projects--beeuniprojects"></a>

### Proyectos — `beeuni.projects`

| método | función |
| --- | --- |
| `list()` · `listForAccount(accountId)` | tus proyectos · proyectos de una cuenta |
| `get(id)` · `detail(id)` | resumen · detalles completos |
| `create(input)` · `createRepo(input)` | a partir de un `repo_url` existente · repositorio nuevo y vacío de GitHub |
| `provision(input)` | proyecto nuevo en un repositorio nuevo gestionado por Beeuni, inicializado con una plantilla de inicio — `{ name, account_id?, seed_starter?, starter_template?, marketplace_items?, source_item_id?, idempotency_key? }` |
| `update(id, input)` · `archive(id)` | actualizar ajustes · archivar |
| `llmCatalog(id)` · `modelPicker(id)` | catálogo de modelos completo · compacto para un selector |
| `sandboxTemplates(id)` · `sandboxHealth(id)` | plantillas de compilación del sandbox · estado de la compilación |
| `sessions(id)` · `createSession(id, input?)` | listar sesiones visibles · crear una sesión |

`provision` crea un proyecto nuevo; no inicia el sandbox de un proyecto
existente. Inicia una sesión en su lugar — consulta [Sesiones](/docs/es/sdk/sessions).

Envía `idempotency_key` cuando pueda haber un reintento — una recarga, una segunda pestaña o un
tiempo de espera agotado tras el que reintentaste. `provision` genera un repositorio gestionado completamente nuevo por llamada, así que
sin una clave todos esos casos crean proyectos duplicados reales. Reutiliza una clave para cada
intento de una misma creación lógica y las repeticiones devolverán el proyecto creado en el primer
intento (201, mismos `project_id` y `push_token: null`). La clave identifica el
intento, no la carga útil — reutilizarla con un `name` diferente devuelve el primer
proyecto e ignora el nuevo valor, así que genera una clave nueva para cada creación distinta.
Crear un segundo proyecto con el mismo **nombre** y sin clave sigue funcionando.

Una repetición que llega mientras la primera llamada aún está aprovisionando recibe `409` con
`code: 'provision_in_flight'` en lugar de un `project_id` que esa llamada aún podría
revertir. Reintenta con la misma clave.

`project(id).sessions.list({ scope: 'project' })` es un inventario del ciclo de vida para
un solicitante con permisos de administrador de proyectos. Añade sesiones accesibles no disponibles, preparadas y eliminadas de forma lógica, con
metadatos de propiedad y estado de ejecución. Ambos ámbitos de listado omiten todas las sesiones que el
solicitante no puede abrir.

`sessions.list` y `sessions.listPage` también aceptan los filtros de jerarquía y
atribución. Sin ellos, la lista sigue siendo plana y no cambia.

| opción | función |
| --- | --- |
| `parent: 'root'` | Solo sesiones de nivel superior. Cada fila contiene `child_count`. |
| `parent: '<sessionId>'` | Las sesiones hijas de esa sesión, de más reciente a más antigua. |
| `startedBy: 'me' \| 'others' \| 'automated'` | Filtrar por `initiator`, en relación con el solicitante. |
| `q` | Búsqueda sin distinción entre mayúsculas y minúsculas por título, etiqueta de iniciador, slug de activador y prefijo del identificador de sesión, en todas las sesiones que el solicitante puede abrir. Con `parent: 'root'`, una raíz que coincide a través de una sesión hija tiene `search_match: 'child'`. |

Cada fila de sesión tiene `parent_session_id` y `initiator`
(`{ type: 'member' | 'trigger' | 'channel' | 'api' | 'system', id, label }`).
`initiator` indica quién inició toda la ejecución; una sesión hija copia el valor de su sesión padre.
`sessionParentId(session)` lee `parent_session_id` y después el valor heredado
`metadata.spawned_by_session`. Un cursor solo es válido para el conjunto de filtros que
lo emitió.

### GitHub — `beeuni.github`

Instalación de GitHub App y vinculación de repositorios con alcance de cuenta, no de proyecto.

| método | función |
| --- | --- |
| `getInstallation(accountId)` · `listInstallations(accountId)` | instalación de esta cuenta · instalaciones a las que el usuario tiene acceso |
| `saveInstallation(input)` · `deleteInstallation(accountId, installationId?)` | registrar · desvincular una instalación |
| `listLinkableInstallations(input)` | instalaciones que el usuario autorizado de GitHub puede vincular; cada entrada incluye `linked_to_other_accounts`, el número de otras cuentas de Beeuni que la tienen, nunca sus nombres |
| `listRepositories(accountId, installationId?)` · `listRepositoryBranches(...)` | repositorios visibles para la instalación · ramas y la predeterminada de GitHub |
| `linkRepository(input)` | Importar un repositorio como proyecto mediante una instalación autorizada de GitHub. |
| `replaceProjectRepository({ project_id, repo_url, expected_repo_url, github_token })` | sustituir el repositorio de GitHub de un proyecto existente. Usa `github_token` con acceso de lectura/escritura a Contents, o `installation_id` junto con `github_user_token` temporal para verificar el acceso de administrador de la organización y conceder acceso solo a este repositorio mediante la GitHub App. `copy_shared_secrets_from_project_id` junto con `copy_shared_secret_identifiers` copia los secretos compartidos de ejecución seleccionados en la misma transacción. Un `expected_repo_url` desactualizado devuelve 409. El repositorio anterior permanece en GitHub. Las sesiones nuevas usan el repositorio de sustitución; las existentes conservan su copia de trabajo. |

Esta lista contiene solo conexiones reales de cuentas. "Beeuni managed" no es una de
ellas: es el backend de la instancia que se describe a continuación.

<a id="instance-git-backend--beeunigitbackend"></a>

### Backend de git de la instancia — `beeuni.gitBackend`

Un único propietario y una credencial para todo el despliegue, usados para los repositorios
administrados por Beeuni. Nunca es una conexión de cuenta ni aparece en
`beeuni.github.listInstallations`.

| método | función |
| --- | --- |
| `get()` | `{ configured, kind: 'app' \| 'pat' \| null, owner }` — cualquier usuario autenticado puede leerlo; solo expone el nombre de usuario del propietario que ya incluye cada `repo_url` administrado |


<a id="billing--beeunibilling"></a>

### Facturación — `beeuni.billing`

Consulta créditos, suscripción, plan e historial de transacciones. Usa el pago y la gestión de facturación de Beeuni para gestionar pagos y compras de créditos.

| método | función |
| --- | --- |
| `accountState(accountId?)` · `accountStateMinimal(accountId?)` | estado de facturación completo · mínimo |
| `transactions(params?)` · `transactionsSummary(params?)` | historial · totales resumidos |
| `creditBreakdown(accountId?)` · `usageHistory(params?)` | saldo de créditos por origen · uso a lo largo del tiempo |
| `sessionCosts.list(options?)` · `sessionCosts.get(sessionId, options?)` | registros paginados de costes de sesiones · registro detallado de una sesión |
| `tierConfigurations()` | niveles de plan disponibles |
| `checkout.createSession(input)` · `checkout.confirmSession(sessionId, accountId?)` | **Obsoletos.** Rechazan con `ENDPOINT_RETIRED`. Se eliminarán en la próxima versión principal. |
| `subscription.createPortalSession(...)` · `subscription.cancel(...)` · `subscription.reactivate(...)` | abrir el portal del cliente · cancelar · reactivar |
| `subscription.cancelScheduledChange(...)` · `prorationPreview(...)` | cancelar · previsualizar un cambio de plan |
| `subscription.scheduleDowngrade(...)` | **Obsoleto.** Rechaza con `ENDPOINT_RETIRED`. Cambia el plan en el portal del cliente. Se eliminará en la próxima versión principal. |
| `credits.purchase(input)` · `credits.autoTopupSettings(...)` · `credits.configureAutoTopup(...)` | compra puntual · consultar · configurar la recarga automática |

`sessionCosts.list()` acepta `accountId`, `projectId`, `limit` y `offset`.
Cada fila combina el coste definitivo del LLM y el coste facturado de cómputo del sandbox.
`sessionCosts.get()` añade el uso del modelo y el registro desglosado de LLM/cómputo.
La lista incluye un total de conciliación de los costes sin sesión.

<a id="sandbox-shares--beeunisandboxshares"></a>

### Enlaces compartidos del sandbox — `beeuni.sandboxShares`

Enlaces públicos para compartir un puerto expuesto del sandbox. Su ámbito es el sandbox, no
el proyecto.

| método | función |
| --- | --- |
| `list(sandboxId)` | enlaces compartidos activos |
| `create(input)` | crear uno — `{ sandboxId, port, ttl?, label? }` |
| `revoke(sandboxId, token)` | revocar uno |

<a id="marketplace-catalog--beeunimarketplace"></a>

### Catálogo del marketplace — `beeuni.marketplace`

Exploración del catálogo público, de solo lectura; distinta de `project(id).marketplace`,
que instala un elemento en un proyecto.

| método | función |
| --- | --- |
| `items(options?)` · `item(id)` · `itemFile(id, path)` | explorar · un elemento · un archivo dentro de un elemento |
| `marketplaces()` · `featured()` | todos los marketplaces · destacados |
| `sources.list()` · `sources.add(input)` · `sources.remove(id)` | listar · añadir · eliminar una fuente |

<a id="notifications--beeuninotifications"></a>

### Notificaciones — `beeuni.notifications`

Las notificaciones del invocador, en todas las cuentas y proyectos. Cada persona con sesión iniciada
tiene una bandeja de entrada, un registro de preferencias y suscripciones Web Push para
sus navegadores. Todos los métodos requieren la credencial de una persona: una cuenta de servicio
recibe `403`.

:::info[Las notificaciones son una función controlada por una bandera]
Beeuni solo genera filas en la bandeja de entrada, Web Push y correos para proyectos con la
bandera `notification_center` activada. Está desactivada por defecto. `list()` omite las filas
de proyectos con la bandera desactivada. `session(pid, sid).watch()` y `.setWatch()`
responden `403` con `{ error, code: "feature_disabled", feature: "notification_center" }`
mientras está desactivada; usa `isFeatureDisabledError(error)` para actuar según su estado.
Las preferencias, las suscripciones Web Push y los tokens de dispositivo son por persona y funcionan
en ambos casos. Con la bandera desactivada, el teléfono del creador de la sesión sigue recibiendo una notificación push
cuando un turno termina, falla, hace una pregunta o solicita permiso. Consulta
[Banderas de funciones](/docs/es/feature-flags) y [Notificaciones](/docs/es/work/notifications).
:::

```ts
const page = await beeuni.notifications.list({ limit: 20 });
await beeuni.notifications.markRead({ ids: [page.notifications[0].id] });
await beeuni.notifications.updatePreferences({ kinds: { turn_done: { push: false } } });
```

| método | función |
| --- | --- |
| `list({ limit?, before? }, { signal? })` | la página más reciente: `{ notifications, unread_count, next_before }`. `limit` va de 1 a 50 (20 por defecto). Pasa `next_before` como `before` para la página siguiente. Se omiten las filas que el invocador ya no puede ver. Un fallo no llama al gestor de errores del host. |
| `markRead({ ids })` · `markRead({ all: true })` · `markRead({ sessionId })` | marcar como leídas de 1 a 100 filas, todas las filas o todas las filas de una sesión. Solo modifica las filas del invocador. Devuelve `{ updated, unread_count }`. |
| `preferences()` · `updatePreferences({ kinds })` | consultar · cambiar la elección de push y correo por tipo. Devuelve `{ kinds, email_available }`. Un parche solo modifica los tipos y canales que especifica. |
| `webPushPublicKey()` | `{ public_key }`: el `applicationServerKey` para `pushManager.subscribe` |
| `registerWebPushSubscription({ endpoint, keys })` | enviar notificaciones push a este navegador. Requiere iniciar sesión en el navegador: un token personal recibe `403`. Un endpoint de un servicio push desconocido recibe `400`. |
| `unregisterWebPushSubscription(endpoint, { signal? })` | dejar de enviar notificaciones push a este navegador. Devuelve `{ deleted }`. |
| `registerDeviceToken(input)` · `unregisterDeviceToken(token, { signal? })` | registrar · eliminar el token push de Expo de una aplicación nativa |

Tipos (`InboxNotificationKind`, enumerados en `INBOX_NOTIFICATION_KINDS`):

| tipo | se envía cuando | push | correo (por defecto) |
| --- | --- | --- | --- |
| `turn_done` | termina un turno que solicitaste o sigues | activado | desactivado |
| `turn_error` | ese turno termina con un error | activado | activado, en un resumen |
| `question` | el agente hace una pregunta | activado | activado, en un resumen |
| `permission` | el agente solicita permiso | activado | desactivado |
| `shared` | alguien comparte una sesión contigo | activado | activado, en un resumen |
| `automation_failed` | una automatización que sigues empieza a fallar | activado | activado, de inmediato |
| `automation_recovered` | esa automatización vuelve a funcionar | activado | desactivado |

`DEFAULT_NOTIFICATION_PREFERENCES` contiene estos valores predeterminados. La fila de la bandeja de entrada
siempre se crea; las preferencias solo determinan las notificaciones push y el correo. Un correo de resumen
incluye las filas que siguen sin leerse 15 minutos después de llegar, con un máximo de un resumen
por hora. Oculta la columna de correo cuando `email_available` sea `false`.

El `url` de cada fila abre su tema. Su parámetro de consulta `?notification=<id>` permite que la
página marque la fila como leída.

El identificador de sesión mantiene el seguimiento del invocador de una sesión:

| método | función |
| --- | --- |
| `session(pid, sid).watch()` | `{ watching }`: si el invocador recibe notificaciones de esta sesión |
| `session(pid, sid).setWatch(watching)` | `false` silencia la sesión para el invocador, `true` vuelve a seguirla. Enviar una instrucción a una sesión activa su seguimiento, pero nunca deshace el silencio. |
| `session(pid, sid).presence({ tab_id, active, alerts? }, { keepalive? })` | concesión de presencia de esta pestaña. `alerts: true` indica que la pestaña muestra sus propias notificaciones: con `notification_center` activado, el servidor omite las notificaciones al teléfono y Web Push solo mientras se usa una pestaña que muestra alertas. Si está desactivado, se ignora `alerts` y cualquier pestaña presente retiene las notificaciones push al teléfono. `keepalive` permite que la solicitud continúe tras cerrar una página. Por defecto: activado para un informe de ausencia, desactivado para uno de presencia. `useSession` envía `keepalive` solo con `presencePageExit`. |

<a id="the-project-handle--beeuniprojectid"></a>

### El identificador de proyecto — `beeuni.project(id)`

Vincula el id del proyecto; todos los subrecursos dependen de él.

```ts
const p = beeuni.project(projectId);
await p.detail();
await p.update({ name });
await p.llmCatalog();
```

| método | función |
| --- | --- |
| `get` · `detail` · `update` · `archive` | leer · detalles completos · actualizar · archivar |
| `llmCatalog` · `modelPicker` · `sandboxHealth` | lecturas del modelo y de la compilación del sandbox |
| `onboardingComplete` | marcar la incorporación al proyecto como completada |
| `validateManifest(raw)` | validar un manifiesto `beeuni.yaml` (o el antiguo `beeuni.toml`) en el servidor |
| `gitToken()` | generar un nuevo token de git push con alcance limitado (`409` para un repositorio propio) |
| `setAgentScope(agentName, scope)` | establecer los secretos y conectores permitidos de un agente en el manifiesto: la segunda vinculación, no un rol |

<a id="ptokens--project-scoped-api-keys"></a>

#### `p.tokens` — claves de API con alcance de proyecto

Se generan automáticamente al crear una sesión como `BEEUNI_TOKEN`; también pueden generarse manualmente.

| método | función |
| --- | --- |
| `list()` | claves de API del proyecto |
| `create(input?)` | generar una nueva |
| `revoke(tokenId)` | revocar una |

<a id="psetuplinks--agent-minted-setup-links"></a>

#### `p.setupLinks` — enlaces de configuración generados por agentes

Un enlace que una persona abre para introducir un secreto o conectar una aplicación, sin acceso completo
al proyecto.

| método | función |
| --- | --- |
| `requestSecret(input)` · `requestConnector(input)` | enlace para recopilar un secreto · conectar una aplicación |

<a id="papps--apps-releases-machines-volumes"></a>

#### `p.apps` — Apps (versiones, máquinas, volúmenes)

Depende de la función de proyecto `apps`. Guía: [SDK → Apps](/docs/es/sdk/apps).

| método | función |
| --- | --- |
| `list()` · `get(appId)` · `create(input)` · `update(appId, input)` · `remove(appId)` | inventario y ciclo de vida; `create` acepta `uses`, `update` también acepta `labels` |
| `deployments.*` · `artifacts.*` · `access.*` · `rollback` · `start` · `stop` | despliegues, artefactos, política de acceso, tráfico y entorno de ejecución |
| `machines.list/start/stop/restart/destroy` · `scale(appId, count)` · `exec(appId, input)` | máquinas por grupo de procesos; `exec` necesita `project.app.admin` |
| `volumes.list/create/update/destroy/snapshots/snapshot/restore/fork` | volúmenes persistentes, instantáneas diarias, restauración, bifurcación |
| `token(appId)` | un token de inicio de sesión de 15 minutos; `409 app_capability_unsupported` sin `member_tokens` |

<a id="psecrets--project-secrets"></a>

#### `p.secrets` — secretos del proyecto

| método | función |
| --- | --- |
| `list()` · `upsert(input)` | listar metadatos · crear o actualizar un valor de solo escritura y una política de entrega |
| `setStrategy(identifier, strategy, options?)` | cambiar la exposición y su lista de hosts |
| `broker(identifier, request)` | ejecutar una solicitud HTTPS autorizada por la sesión y sujeta a una política |
| `remove(identifier)` | eliminar un secreto |
| `setPersonal(name, value)` · `removePersonal(name)` | establecer · eliminar una anulación por usuario |
| `setGitCredential(input)` | establecer una credencial de autenticación de git |

`runtime` con el consumidor `sandbox` es la exposición de **entorno**: la predeterminada,
y la única política que coloca un valor en texto plano en la sesión. `egress` con
el consumidor `network` es la exposición **controlada a la salida**: la sesión conserva un identificador
y Beeuni sustituye el valor real fuera del sandbox, para los hosts HTTPS exactos
que enumera la política. La exposición controlada a la salida es experimental; necesita la
marca de función `secrets_egress` (Settings → Feature flags). Con la marca desactivada,
`setStrategy(identifier, 'egress', …)` y `upsert(...)` con una política de salida
devuelven `403` `feature_disabled`. Ningún consumidor `broker` tiene presencia
alguna en la sesión. Consulta
[Secretos](/docs/es/project/secrets). El método `broker(...)` requiere un
token con alcance de sesión y un identificador de sesión activo.

<a id="paccess--project-assignments-invites-requests"></a>

#### `p.access` — asignaciones, invitaciones y solicitudes del proyecto

Cada método aquí lee o escribe una asignación con alcance limitado a este proyecto. Los roles
del proyecto son `manager` y `member`.

| método | función |
| --- | --- |
| `list()` · `invite(email, role)` | entidades con acceso · invitar a un usuario |
| `update(userId, role)` · `revoke(userId)` | asignar un rol de proyecto · revocar la asignación |
| `pendingInvites()` · `requests()` | invitaciones pendientes · solicitudes de acceso pendientes |
| `resendInvite(inviteId)` · `revokeInvite(inviteId)` · `approveRequest(id)` · `rejectRequest(id)` | reenviar/revocar una invitación · aprobar/rechazar una solicitud |
| `groupGrants()` · `attachGroupGrant(...)` · `updateGroupGrant(...)` · `detachGroupGrant(...)` | las mismas asignaciones, con una entidad `group` |

`p.access.resourceGrants` es la vista de **asignación de objetos**: limita una
entidad a un objeto del proyecto en lugar de a todo el proyecto. Actualmente, Beeuni
aplica las asignaciones de objetos a agentes y habilidades. Un agente está cerrado por
defecto: un miembro solo puede acceder a él cuando una asignación lo nombra a él o a uno de sus
grupos. Una asignación de objeto no incluye permisos propios y restringe a un
gestor del proyecto tanto como a un miembro.

| método | función |
| --- | --- |
| `resourceGrants.list()` | asignaciones de objetos en este proyecto |
| `resourceGrants.create(input)` | asignar un objeto a un usuario o grupo |
| `resourceGrants.remove(grantId)` | revocar una asignación de objeto |

<a id="pconnectorslug--one-connector"></a>

#### `p.connector(slug)` — un conector

| método | función |
| --- | --- |
| `run(action, args, options?)` | la salida de la acción; lanza `ConnectorCallError` o `ConnectorApprovalPendingError` — consulta [Errores](/docs/es/sdk/reference#pconnectorslug--one-connector) |
| `call(action, args, options?)` | el resultado sin procesar de la llamada; `202` se resuelve con `ok: false` |
| `describe()` · `describe(action)` | el conector con esquemas de entrada y salida · una acción |
| `accounts()` | las cuentas con las que quien hace la llamada puede ejecutarlo, la predeterminada primero |
| `paginate(action, args, { next, maxPages? })` | iterador asíncrono sobre la salida de cada página; lanza `ConnectorPageLimitError` con `nextArgs` cuando hay más de `maxPages` páginas (100 por defecto) — consulta [Páginas](/docs/es/sdk/reference#pconnectorslug--one-connector) |
| `uploadAttachment(content, input)` | `p.connectors.uploadAttachment` con `connector` establecido en este slug |

`options`: `account`, `approvalContext`, `timeoutMs` (90 000 por defecto), `signal`. Con el archivo de `beeuni connectors types`, los argumentos y la salida tienen tipos definidos.

<a id="pconnectors--tool-connectors"></a>

#### `p.connectors` — conectores de herramientas

| método | función |
| --- | --- |
| `catalog()` · `tools()` | catálogo de Connector invocables · herramientas `<connector>.<action>` aplanadas |
| `search(query, options?)` · `describe(tool)` | buscar · inspeccionar una herramienta invocable |
| `call(tool, args?, options?)` | llamar a una herramienta `<connector>.<action>` a través de la pasarela del servidor. `options.account` indica con qué cuenta ejecutarla — consulta más abajo. |
| `accounts(slug)` | las cuentas con las que quien hace la llamada puede invocar `slug`, la predeterminada primero — con las que se compara `options.account` |
| `uploadAttachment(content, input)` | preparar bytes para `input.connector`; devuelve `ref` (`{ $beeuni_attachment }`) para incluirlo en los argumentos de una llamada posterior — consulta [Adjuntar archivos](/docs/es/connect/connectors#attach-files) |
| `list()` · `config(connectorId)` | conectores configurados · configuración de un conector |
| `create(input)` | añadir un conector |
| `auth.discover(input)` | previsualizar la autenticación a partir de una especificación OpenAPI, una colección Postman o un endpoint |
| `remove(connectorId)` · `sync()` | eliminar un conector · resincronizar conectores |
| `setName(connectorId, name)` · `setSensitive(connectorId, sensitive)` | renombrar · marcar como sensible (control de aprobación adicional) |
| `setAuthorizationStrategy(slug, strategy)` | @deprecated. Sigue llamando a la ruta; la ruta no realiza ninguna operación, responde `{ok:true, deprecated:true}` y no cambia nada. Ahora `owner_type` en cada conexión constituye toda la regla de acceso. |
| `setCredentialMode(connectorId, mode)` · `setCredential(connectorId, input)` | cambiar el origen · establecer el valor de la credencial |
| `policies.get(connectorId)` · `policies.set(connectorId, policies)` | leer · reemplazar sus políticas de herramientas |
| `connections.list()` · `connections.reconcile(input)` | listar · crear/actualizar cuentas conectadas |
| `connections.updateCredential(connectionId, input)` | rotar una credencial de conexión |
| `connections.revoke(connectionId)` · `connections.activate(connectionId)` | denegar · restablecer una conexión |
| `connections.setDefault(connectionId)` | establecerla como predeterminada para el ámbito de su propietario |
| `connections.rename(connectionId, label)` | cambiar solo la etiqueta; la cuenta sigue autorizada. Cada conexión incluye `connected_as`, la identidad con la que se autorizó. |

`p.connectors.discover` permite explorar el catálogo de conectores directos. Es **experimental** y está desactivado por defecto: actívalo por proyecto en [Settings → Feature flags](/docs/es/feature-flags) → "Connectors API Discover". Easy Connect sigue siendo el mercado de conectores predeterminado.

| método | función |
| --- | --- |
| `discover.list(query?, cursor?)` · `discover.detail(id)` | buscar entradas OpenAPI/MCP/GraphQL/CLI · detalles de una entrada |

`p.connectors.pipedream` es la vía de compatibilidad de OAuth gestionado anterior; `listApps`
solo devuelve aplicaciones OAuth. Usa Easy Connect para nuevas integraciones gestionadas. Conecta directamente las aplicaciones que usan claves API.

| método | función |
| --- | --- |
| `pipedream.listApps(params?)` · `pipedream.connect(input)` · `pipedream.finalize(input)` | explorar el catálogo de aplicaciones · iniciar un flujo de conexión · finalizarlo |

Un conector define la herramienta, la aplicación del proveedor y las políticas. `connections`
almacena sus **cuentas** conectadas: un conector puede contener varias a la vez.
Una sesión puede fijar una para todas las llamadas con
`connector_bindings: { alias: { connection_id } }`, o una llamada individual puede indicar
una con `call(tool, args, { account })`. Las credenciales permanecen cifradas y
se resuelven en cada solicitud.

`connections` es la única fachada de autorización activa. Los nombres retirados
`authorizations` y `profiles` no forman parte de la interfaz actual del SDK.

<a id="accounts-and-the-call-time-gate"></a>

#### Cuentas y control de acceso al realizar la llamada

Cada conexión tiene un `owner_type`: `project` (compartida: accesible para cualquiera
a quien se conceda acceso al conector, ya sea una persona o una cuenta de servicio) o `member` (privada:
accesible solo para su propietario, y solo en una sesión privada; nunca para una
cuenta de servicio). Una cuenta compartida puede restringirse a grupos y personas:
`connections.list()` devuelve sus permisos en `shared_with`, y
`connectionSharedWithEveryone(connection)` indica si todos los miembros del
proyecto pueden usarla. Una cuenta restringida sigue las reglas de las cuentas privadas para las
personas que especifica, y `usable: false` marca una cuenta que quien realiza la llamada solo puede ver para administrarla.
Concede o revoca acceso con `createAssignment` / `revokeAssignment`
(`object: { type: 'connection', id }`); consulta
[Elegir quién puede usar una cuenta compartida](/docs/es/connect/connectors#choose-who-can-use-a-shared-account).
`p.connectors.accounts(slug)` enumera todas las cuentas a las que puede acceder quien realiza la llamada,
con la predeterminada en primer lugar:

```ts
const accounts = await beeuni.project(projectId).connectors.accounts('stripe-read');
// [{ connection_id, label, owner_type, is_default }, ...]

await beeuni.project(projectId).connectors.call(
  'stripe-read.get_balance',
  {},
  { account: 'My Stripe' }, // a connection id, a label, or the words `me` / `project`
);
```

Una llamada que no indique ninguna cuenta se resuelve primero a la cuenta privada predeterminada
de quien la realiza y, después, a la cuenta compartida predeterminada del proyecto. Si una cuenta indicada
no se resuelve, la llamada se **deniega** con `connector_not_connected`; nunca
se sustituye silenciosamente, y la denegación enumera `requested_account` y
`available_accounts`. Cada llamada correcta devuelve la cuenta con la que se ejecutó:
`result.account = { connection_id, label, owner_type }`.

Ya no hay un control de acceso a conectores al crear una sesión. Antes se
rechazaba una sesión con `409 CONNECTOR_CONNECTION_REQUIRED` cuando un conector obligatorio no tenía una
conexión utilizable; ese rechazo se ha eliminado (`require_connectors` no tiene efecto; consulta
[`p.sessions`](#psessions--and-the-session-handle)). El control se ha trasladado a la
llamada: una `connector_not_connected` denegada incluye `connect_url`, un enlace de
autorización alojado, siempre que se pueda generar para quien realiza la llamada. La solución
requiere un solo clic y se muestra mediante el botón de conexión de la transcripción.

<a id="ppolicies--project-policies"></a>

#### `p.policies` — políticas del proyecto

| método | función |
| --- | --- |
| `list()` · `set(policies)` | las políticas del proyecto · sustituir el conjunto |

<a id="ptriggers--cron-webhook-monitor-and-event-automations"></a>

#### `p.triggers` — automatizaciones de cron, webhook, monitor y eventos

Un disparador inicia una acción del agente según una programación, un webhook entrante, un comando supervisado o un evento de aplicación en un conector. Consulta
[Disparadores](/docs/es/connect/triggers) para conocer la estrategia de sesión y las
plantillas de carga útil.

| método | función |
| --- | --- |
| `list()` | todos los disparadores |
| `eventTypes({ connector })` | los eventos de aplicación que pueden activar un disparador en un conector (`{ provider, app, event_types }`); 404 para un conector desconocido, 409 `event_source_unavailable` cuando su proveedor no tiene una fuente de eventos |
| `create(input)` | crear uno |
| `update(triggerId, input)` | editar un disparador |
| `remove(triggerId)` | eliminar un disparador |
| `fire(triggerId)` | ejecutarlo ahora |
| `setActivation(paused)` | pausar o reanudar todos los disparadores del proyecto |

`create(input)` acepta `{ name, type: 'cron' | 'webhook' | 'monitor' | 'event', prompt_template,
slug?, agent?, model?, enabled?, session_mode?, session_id?, cron?, run_at?,
timezone?, secret_env?, connector?, event?, event_source?, event_account?, event_config?, session_access? }`. `name` and `prompt_template` son obligatorios.
`cron`/`run_at` son mutuamente excluyentes (`type: 'cron'`); `secret_env` (el
secreto HMAC del webhook) se aplica a `type: 'webhook'`.

Un disparador `event` necesita `connector` (el slug de un conector) y `event` (un identificador de tipo
de `eventTypes`). `event_account` es opcional: la etiqueta de una cuenta compartida de
ese conector; `null` o su omisión utiliza la cuenta compartida predeterminada del conector.
`event_source` es opcional: el identificador del adaptador de la fuente de eventos, como `composio`. `null` o su omisión utiliza el
proveedor del conector. El identificador de tipo pertenece a ese adaptador. La estructura de `event_config` viene determinada por el `config_schema` de ese tipo.
`eventTypes` y `eventApps` indican el adaptador como `source`; `provider` es un alias obsoleto.
Cada disparador enumerado incluye `event: { connector, account, connected_as, type, config, source, provider, app,
status: 'active' | 'needs_connection' | 'error' | 'pending', error,
last_event_at } | null`. Las plantillas de instrucciones leen el evento como
`{{ event.data.<field> }}` junto con `event.id`, `event.type`, `event.app`,
`event.connector` y `event.occurred_at`.

`session_access` controla quién puede abrir las sesiones que crea el disparador. Es
`{ mode: 'private' | 'members' | 'project', memberIds: string[], groupIds:
string[] }` and defaults to `private`. Esta política es un estado de ejecución
local de la cuenta. No se incluye en el manifiesto portátil `beeuni.yaml`. Actualizar solo
`session_access` no crea ningún commit de Git. Una sesión fijada conserva sus propios ajustes
para compartir. Un administrador del proyecto siempre puede abrir las sesiones creadas por disparadores, incluidas
las que usan `private` o acceso para miembros seleccionados.

`session_access` es un ajuste de visibilidad por recurso que se añade al modelo de roles,
no un rol. Decide quién puede abrir las sesiones de un disparador. No concede ningún
permiso que la evaluación del rol deniegue. Consulta
[Cuentas y acceso](/docs/es/accounts#per-feature-access-settings).

<a id="pmarketplace--pregistry--installed-items"></a>

#### `p.marketplace` / `p.registry` — elementos instalados

Instala los archivos de un elemento del catálogo en la rama predeterminada del proyecto.
`registry.*` es un alias idéntico de `marketplace.*`.

| método | función |
| --- | --- |
| `marketplace.list()` · `marketplace.install(id)` | elementos instalados · instalar un elemento del catálogo |
| `marketplace.updates()` · `marketplace.update(name)` · `marketplace.updateAll()` | actualizaciones disponibles · actualizar uno · actualizar todos |
| `marketplace.remove(name)` | desinstalar un elemento |

<a id="pfiles--repo-files-read"></a>

#### `p.files` — archivos del repositorio (lectura)

Acceso de solo lectura al árbol de Git del proyecto. Para leer y escribir archivos dentro de una
sesión en ejecución, usa las operaciones de archivos de la sesión; consulta
[Archivos](#files) en Módulos más abajo.

| método | función |
| --- | --- |
| `listDirectory({ ref?, path? })` | un nivel de carpeta: `{ entries: [{ path, type, size? }], truncated }`. Úsalo para construir un árbol; cada carpeta está completa hasta 5.000 entradas. |
| `list(options?)` · `read(path, ref?)` | todos los archivos de una ruta, de forma recursiva, con un límite de 1.000 (la respuesta incluye `X-Beeuni-Truncated: 1` cuando se trunca) · el contenido de un archivo en una referencia de Git |
| `search(query, { content?, ref?, limit? })` | buscar nombres de archivo en todo el repositorio, o contenido con `content: true` |
| `archive(options?)` · `history(path)` | descargar un archivo tar · el historial de Git de un archivo |

<a id="pgit--history"></a>

#### `p.git` — historial

| método | función |
| --- | --- |
| `commits()` | el registro de commits |
| `commit(sha)` · `commitDiff(sha)` | un commit · sus diferencias |
| `branches()` · `versionDiff(from, to)` | ramas · diferencias entre dos referencias |

<a id="pchangerequests--lifecycle-and-merge"></a>

#### `p.changeRequests` — ciclo de vida y fusión

Una solicitud de cambio (CR) permite fusionar el trabajo de una sesión en la rama predeterminada.

| método | función |
| --- | --- |
| `list()` · `get(crId)` | CR abiertas · una CR |
| `diff(crId)` · `mergePreview(crId)` | sus diferencias · previsualizar el resultado de la fusión |
| `open(input)` · `merge(crId, input?)` | abrir · fusionar una CR |
| `close(crId, input?)` · `reopen(crId, input?)` | cerrar sin fusionar · reabrir una cerrada |
| `requestChanges(crId, input)` | registrar comentarios, que opcionalmente se envían a la sesión de origen |

<a id="psessions--and-the-session-handle"></a>

#### `p.sessions` — y el identificador de sesión

| método | función |
| --- | --- |
| `list()` | las sesiones del proyecto |
| `create(input?)` | crear una sesión |
| `session(sid)` | el manejador de sesión (igual que `beeuni.session(id, sid)`) |

El manejador de sesión es el núcleo del entorno de ejecución — consulta
[Sesiones](/docs/es/sdk/sessions).

`create(input)` acepta `connector_bindings` con claves basadas en el slug de conexión del conector.
Cada valor identifica un `connection_id`. También acepta `secrets` para
restringir los secretos originados en el backend. `require_connectors` está obsoleto y
no tiene efecto: el campo se sigue aceptando (nunca se rechaza un cliente antiguo por
enviarlo), pero nada lo lee — nunca se rechaza una sesión al crearla
por un conector sin una cuenta utilizable. Consulta [Cuentas y el control en el momento de la llamada](#accounts-and-the-call-time-gate).

<a id="papprovals--the-connector-approval-inbox"></a>

#### `p.approvals` — bandeja de aprobaciones de conectores

Acciones sujetas al control de conectores pendientes de una decisión — sustenta la
experiencia de aprobación de permisos (`APPROVE` / `ASK` / `BLOCK`).

| método | función |
| --- | --- |
| `list(options?)` · `sessionsNeedingInput(options?)` | aprobaciones pendientes · sesiones bloqueadas a la espera de una decisión |
| `resolve(executionId, decision, scope?)` | aprobar o denegar una — `decision: 'approve' \| 'deny'`, `scope: 'once' \| 'session' \| 'session_all'` |

<a id="pgateway--llm-observability"></a>

#### `p.gateway` — observabilidad de LLM

Registros de solicitudes, agregados de costes/latencia, presupuestos y claves de API de la pasarela para el
tráfico de modelos de este proyecto.

| método | función |
| --- | --- |
| `logs(opts?)` · `log(logId)` | entradas del registro de solicitudes · una entrada del registro |
| `overview(days?)` · `series(days?)` · `breakdown(days?)` · `sessions(days?)` · `errors(days?)` | agregados, coste por sesión y errores en un intervalo |
| `budgets()` · `setBudget(input)` · `deleteBudget(budgetId)` | leer · crear/editar · eliminar un presupuesto |
| `keys()` · `createKey(name)` · `revokeKey(keyId)` | listar · emitir · revocar una clave de API de la pasarela |
| `playground(prompt, models)` | ejecutar un prompt en hasta 6 modelos |
| `routing.get()` · `routing.set(policy)` · `routing.reset()` | leer · reemplazar · heredar la política de enrutamiento |
| `routing.preview(input)` | resolver una ruta sin invocar un modelo |

Una política de enrutamiento contiene un modelo predeterminado, un modelo de visión y una cadena
ordenada de alternativas; cada modelo se intenta como máximo una vez. `fallbackOn` es
`transient` o `any-error`.

La pasarela pasa al siguiente modelo alternativo solo cuando el modelo actual
falla antes de devolver cualquier salida:

- `transient`: un error de red, un tiempo de espera agotado, cualquier `5xx`, o `402`, `403`, `429`.
- `any-error`: cualquier fallo, incluido un `400`.

Un fallo después de que empiece la salida permanece en ese modelo. Una solicitud con tu propia
clave de proveedor (BYOK) nunca recurre a un modelo facturado por Beeuni. Cada modelo
alternativo recibe sus propios valores predeterminados de generación.

<a id="pchannels--slack--email--voice"></a>

#### `p.channels` — Slack / correo electrónico / voz

Interfaces de conectores que permiten a un agente actuar como una aplicación de Slack, una dirección de correo electrónico
o unirse a una llamada de voz en tiempo real.

| método | función |
| --- | --- |
| `slack.installation()` · `slack.mode()` · `slack.manifest()` | instalación actual · modo · manifiesto de la aplicación |
| `slack.connect(input)` · `slack.disconnect()` | conectar · desconectar |
| `slack.getFile(url)` · `slack.uploadFile(input)` | descargar · subir un archivo a través del proxy del servidor |
| `email.installation(connectorSlug?)` · `email.mode()` | instalación actual · modo |
| `email.connect(input)` · `email.disconnect(...)` · `email.updatePolicy(input)` | conectar · desconectar · actualizar la política de envío/respuesta |
| `voice.setBotName(name)` | cambiar el nombre del bot en una llamada en curso |

<a id="pmodeldefaults--default-model-preferences"></a>

#### `p.modelDefaults` — preferencias de modelo predeterminado

Valores predeterminados de modelo a nivel de cuenta, agente y proyecto, resueltos por la pasarela.

| método | función |
| --- | --- |
| `get()` · `set(input)` · `clear(params)` | leer · establecer un valor predeterminado · borrar una anulación |

<a id="psetdefaultagent--project-default-agent"></a>

#### `p.setDefaultAgent` — agente predeterminado del proyecto

`p.setDefaultAgent(agentName)` comprueba que el agente esté declarado y habilitado,
y luego lo establece como `default_agent` en el `beeuni.yaml` del proyecto. Las sesiones nuevas
prefieren este agente salvo que un usuario elija otro.

<a id="pupdatefeatureflag--feature-flags"></a>

#### `p.updateFeatureFlag` — indicadores de funcionalidades

`p.updateFeatureFlag(feature, enabled)` activa o desactiva un indicador de funcionalidad para el
proyecto. Pasa `enabled: null` para borrar la anulación y usar el
valor predeterminado de la plataforma. Llama al `PATCH /v1/projects/:id/features` canónico.

`feature` es uno de los `FEATURE_FLAG_KEYS` (exportados desde `@beeuni/sdk`, con el tipo
`FeatureFlagKey`). Quien realiza la llamada necesita el permiso `project.settings.write` del proyecto;
de lo contrario, la ruta responde con `403`.

Todas las rutas controladas por indicadores rechazan las solicitudes del mismo modo mientras el indicador está desactivado: HTTP `403`
con `{ error, code: "feature_disabled", feature }`. Usa `isFeatureDisabledError(error)`
para bifurcar según este valor y `featureDisabledKey(error)` para leer la clave del indicador — nunca
hagas comparaciones con el texto del mensaje.

`p.updateExperimentalFeature(feature, enabled)` es el alias **obsoleto**.
Sigue llamando al alias de ruta obsoleto `PATCH /v1/projects/:id/experimental`
para que los consumidores fijados a una API desplegada más antigua sigan funcionando. Usa
`p.updateFeatureFlag` en el código nuevo.

<a id="psandbox--templates-and-snapshot-builds"></a>

#### `p.sandbox` — plantillas y compilaciones de instantáneas

Configuración de compilación de entornos aislados más allá de `sandboxHealth`/`sandboxTemplates` en el
manejador del proyecto: plantillas de Dockerfile/imagen/pool precalentado y sus compilaciones de instantáneas.

| método | función |
| --- | --- |
| `list()` · `snapshots()` | entornos aislados de este proyecto · instantáneas compiladas |
| `rebuildSnapshot(slug?)` · `fixWithAgent()` | recompilar una instantánea · pedir a un agente que corrija una compilación fallida |
| `createTemplate(input)` · `updateTemplate(...)` · `removeTemplate(...)` · `buildTemplate(...)` | añadir · editar · eliminar · compilar una plantilla |
| `setProvider(provider)` | solicitar un cambio de proveedor. `null` (o el valor predeterminado de la plataforma / el proveedor ya activo) se aplica inmediatamente; cambiar a otro proveedor habilitado inicia una transición duradera de preparar→verificar→activar — el proveedor actual sigue prestando servicio mientras se compila y verifica la imagen precalentada de destino, que después se activa. El valor devuelto es una unión etiquetada: `kind:'project'` (inmediato) o `kind:'preparation'` (consultar periódicamente `getProjectSandboxProviderTransition()` hasta `activated`/`failed`) |

<a id="platform-admin-hooks-beeunisdkreact"></a>

### Hooks de administración de la plataforma (`@beeuni/sdk/react`)

Estos hooks sirven a la consola de administración. La API solo responde a ellos para un administrador
de la plataforma. Cada mutación invalida las consultas `['admin', 'accounts']`.

| Hook | Qué hace |
| --- | --- |
| `useAdminRole(userId, options?)` | leer el rol de quien realiza la llamada desde `GET /user-roles` → `{ isAdmin, role }`. `userId` define la clave de caché. La ausencia de `userId` deshabilita la consulta. Vuelve a consultar al montar y al recibir el foco, por lo que un rol revocado deja de controlar el acceso en una sola navegación. |
| `useAdminGrantCredits()` | mutación `{ accountId, amount, description, isExpiring }`: añadir crédito al monedero. `POST /admin/api/accounts/:id/credits`. Mueve dinero: confirmar primero en la interfaz. |
| `useAdminDebitCredits()` | mutación `{ accountId, amount, description }`: retirar crédito del monedero. `POST /admin/api/accounts/:id/credits/debit`. Mueve dinero: confirmar primero en la interfaz. |
| `useAdminSetTier()` | mutación `{ accountId, tier }`: establecer el nivel del plan. |
| `useAdminGrantTrial()` | mutación `{ accountId, tierKey, seats, durationDays, creditGrant?, note? }`: conceder o reemplazar un periodo de prueba. La cuenta se comporta como `tierKey` hasta que termina el periodo. `creditGrant` también añade crédito al monedero. |
| `useAdminRevokeTrial()` | mutación `{ accountId }`: finalizar ahora un periodo de prueba activo. Responde con `400` cuando no hay ninguno activo. |

<a id="escape-hatch"></a>

### Vía de escape

`beeuni.runtime()` devuelve el cliente REST tipado de runtime para el entorno
 aislado activo.
En un cliente creado por `createScopedBeeuni` (`@beeuni/sdk/server`), **lanza una excepción**
— el entorno de ejecución "activo" global del proceso es el entorno aislado de otra solicitud en un
servidor multiinquilino, lo que supone una filtración entre inquilinos. Usa en su lugar
`beeuni.session(pid, sid).runtime`, limitado a la sesión (llama primero a `ensureReady()`), que
resuelve el entorno aislado propio de esa sesión.

<a id="modules"></a>

## Módulos

Los módulos independientes de frameworks que sustentan la fachada de [el cliente](#the-client) y los
hooks de React. Úsalos cuando necesites una operación sin la fachada,
una función auxiliar pura o una capa de aislamiento exclusiva de Node. Cada módulo tiene un
nivel de estabilidad para que sepas sobre qué construir.

| Nivel | Significado |
| --- | --- |
| Canónico | Importa desde la raíz `@beeuni/sdk`. Úsalo para todo el código nuevo. |
| Compatible | Una subruta dedicada (`@beeuni/sdk/react`, `@beeuni/sdk/server`). De primera clase, no obsoleta. |
| Alias obsoleto | Una subruta antigua que sigue funcionando. Reexporta código que la raíz ya exporta. Importa desde la raíz en su lugar. |
| Interno | Fuera de semver. No lo importes en el código del host. |

El punto de entrada raíz es el canónico. Todos los nombres independientes de frameworks que aparecen a continuación se pueden importar
directamente desde `@beeuni/sdk`:

```ts
import { files, getSessionHealth, getClient, authenticatedFetch, backendApi } from '@beeuni/sdk';
```

<a id="canonical-modules"></a>

### Módulos canónicos

| Módulo | Qué hace |
| --- | --- |
| Archivos | Operaciones con archivos del espacio de trabajo: listar, leer, buscar, escribir |
| Entorno de ejecución de sesión | Comprobación de estado y generadores de URL de vista previa/proxy |
| Cliente runtime | El cliente REST tipado de runtime y todos sus tipos |
| Autenticación | `authenticatedFetch` y funciones de acceso al token |
| REST de proyectos | Las funciones REST sin procesar que encapsula la fachada |
| Cliente API | `backendApi`, el cliente HTTP tipado de bajo nivel |
| Turnos | Agrupación de mensajes en turnos, cálculos de costes y estados — consulta [Turnos](#turns) |
| Transcripciones | `formatTranscript`, una exportación a Markdown del lado del cliente |
| Listas del editor | Las listas de agentes y modelos del editor de sesión: cuáles ofrecer y cuál se ejecuta |

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

#### Archivos

```ts
import { files } from '@beeuni/sdk';

const tree = await files.list('/workspace/src');
const { content } = await files.read('/workspace/README.md');
const hits = await files.findText('TODO');
await files.upload(file, '/workspace/uploads', file.name, {
  onProgress: ({ loadedBytes, totalBytes }) => {
    console.log(`${loadedBytes}/${totalBytes}`);
  },
});
```

Las cargas de más de 64 KiB cruzan el límite del sandbox en bloques verificados. La
función de devolución de llamada de progreso se ejecuta después de que cada bloque llegue al sistema de archivos de la sesión.

`uploadNativeFile({ uri, name, type? }, targetPath, baseUrl)` carga un archivo del
dispositivo desde React Native en una sola solicitud. El entorno de ejecución transmite los bytes desde
`uri`, por lo que nunca entran en el heap de JS. Usa la misma autenticación, plazo límite y
repetición de `401` que `files.upload`. No divide en bloques.

`fetchProjectArchive`, `fetchCostExportCsv` y las llamadas de versión del sandbox envían
mediante el mismo transporte. Un fallo lanza un `ApiError` con `status`.

`files.write(path, blob)` reemplaza el archivo en `path` y falla si `path` es una
carpeta. `files.create(path)` crea un archivo vacío y nunca sobrescribe: cuando ya
existe un archivo o una carpeta en `path`, lanza `FileExistsError`
(`status` `409`, `code` `'FILE_EXISTS'`).

```ts
import { FileExistsError, files } from '@beeuni/sdk';

try {
  await files.create('/workspace/notes.md');
} catch (error) {
  if (error instanceof FileExistsError) console.log(`${error.path} already exists`);
  else throw error;
}
```

`files` apunta al sandbox activo globalmente. Si tu host ejecuta más de una
sesión a la vez, llama a `s.files` en el identificador de sesión en su lugar. Siempre
apunta al sandbox propio de esa sesión. Consulta [Sesiones](/docs/es/sdk/sessions).

<a id="session-runtime-helpers"></a>

#### Funciones auxiliares del entorno de ejecución de sesión

```ts
import { getSessionHealth, isRuntimeReady } from '@beeuni/sdk';

const result = await getSessionHealth();
if (result.ok && isRuntimeReady(result.health)) {
  // the sandbox daemon is ready
}
```

`getSessionHealth` nunca lanza una excepción ante un estado que no sea 2xx. Devuelve
`{ status, ok, health, body }` y te permite decidir qué significa un estado. El
mismo módulo exporta las funciones auxiliares de URL que reescriben la salida `localhost` de un agente
como una URL de proxy accesible: `detectLocalhostUrls`, `rewriteLocalhostUrl`,
`proxyLocalhostUrl`, `parseLocalhostUrl` y `buildWebProxyUrl`.

<a id="opencode-client"></a>

#### Cliente runtime

```ts
import { getClient } from '@beeuni/sdk';

const client = getClient();
const { data } = await client.session.list({ limit: 100 });
```

`getClient()` devuelve el cliente de compatibilidad tipado de runtime para el sandbox activo,
con la autenticación ya inyectada. Prefiere `beeuni.session(pid, sid).runtime`, el
mismo cliente limitado a una sesión, al `getClient()` global cuando tu
host ejecute más de una sesión.

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

#### Funciones auxiliares de autenticación

```ts
import { authenticatedFetch, getAuthToken } from '@beeuni/sdk';

const res = await authenticatedFetch(`${runtimeUrl}/beeuni/health`);
const token = await getAuthToken();
```

El token procede de la función `getToken` que pasaste a `createBeeuni`.
La mayor parte del código de la aplicación no necesita este módulo: las capas de archivos, sesiones y fachada
ya gestionan la autenticación por ti.

<a id="api-client"></a>

#### Cliente API

```ts
import { backendApi } from '@beeuni/sdk';

const data = await backendApi.get('/some/endpoint');
await backendApi.post('/some/endpoint', { name: 'x' });
```

`backendApi` es el cliente HTTP tipado en el que se basa cada función REST. Úsalo
solo para un endpoint que aún no tenga un envoltorio tipado.

<a id="composer-agent-and-model-lists"></a>

#### Listas de agentes y modelos del editor

Crea los selectores de agentes y modelos de un editor de sesión con las mismas reglas
que usa la aplicación web de Beeuni. Todas las funciones son puras: obtén las entradas con el
cliente y luego pásalas.

```ts
import {
  composerSelectableAgents,
  createModelVisibility,
  flattenModels,
  modelInDefaultView,
  pickerProviderList,
  projectConfigAgentsToRuntimeAgents,
  resolveComposerAgent,
  resolveComposerModel,
  resolveModelDefault,
} from '@beeuni/sdk';

// Agents: the roster, the picker list, and the agent the send carries.
const roster = projectConfigAgentsToRuntimeAgents(detail.config);
const pickerAgents = composerSelectableAgents(roster, { enableProjects: false });
const agent = resolveComposerAgent({
  agents: roster,
  boundAgent: session?.agent,
  defaultAgent: detail.config.default_agent,
  selectedAgent: pick,
});
if (agent.disabled) {
  // no accessible agent: refuse the send
}

// Models: gateway projects read /model-picker, native projects merge the
// catalog synthesis with the running sandbox's provider list.
const gatewayEnabled = detail.project.experimental?.llm_gateway === true;
const providers = pickerProviderList({
  gatewayEnabled,
  modelPicker,
  runtimeProviders,
  llmCatalogProviders,
  secretNames,
});
const models = flattenModels(providers, { providerMode: gatewayEnabled ? 'gateway' : 'native' });
const isVisible = createModelVisibility({ catalogModels: models });
const defaultView = models.filter(
  (m) => m.enabled !== false && modelInDefaultView(m, { search: '', isStoreVisible: isVisible, selected }),
);
const { model, explicit } = resolveComposerModel({
  models,
  picks: [sessionPick],
  serverDefault: resolveModelDefault(modelDefaults, agent.selected ?? undefined),
  providers,
});
```

`resolveComposerModel` devuelve `explicit: undefined` cuando el editor usa
su valor predeterminado. `model` es la clave que se muestra y se envía. El
`id` de cada modelo aplanado es la referencia que almacena una selección; `modelRefToKey(ref, gatewayEnabled)` convierte una
referencia almacenada (una fijación de sesión, una vinculación de canal, el `model` de un activador) de nuevo en una
clave.

<a id="supported-subpaths"></a>

### Subrutas compatibles

| Subruta | Qué hace |
| --- | --- |
| `@beeuni/sdk/react` | Hooks de React — consulta [Hooks de React](/docs/es/sdk/react) |
| `@beeuni/sdk/server` | Configuración por solicitud para backends multiinquilino |
| `@beeuni/sdk/wire-message-id` | Solo el reloj de identificadores de mensajes del protocolo de runtime (un módulo, sin importaciones). La raíz exporta los mismos nombres |

<a id="wire-message-ids"></a>

#### Identificadores de mensajes del protocolo

El `messageId` de un prompt es su posición en la transcripción. runtime ordena
los mensajes por `msg_` + un reloj de 48 bits (`Date.now() * 0x1000`, los 48 bits inferiores) +
14 caracteres base62, y el reloj se reinicia cada 2^36 ms (unos 2,2 años).
Nunca lo codifiques a mano.

```ts
import { mintWireMessageId } from '@beeuni/sdk';

// `after`: the ids already in this session's transcript.
const messageId = mintWireMessageId({ after: transcriptIds });
```

La generación se fecha dos minutos atrás y luego se eleva un tic por encima del identificador más reciente
en `after` que esté como máximo una hora por delante. Un llamador que conserva la posición más reciente
como un reloj en lugar de identificadores usa `mintWireMessageIdAbove({ nowMs,
newestKnownTime })`, which returns `{ id, time }`. `newestWireIdClock(ids)`
para encontrar ese reloj. Todas las comparaciones se realizan en el anillo, por lo que los identificadores a ambos lados de un
reinicio conservan su orden real.

El reloj de 48 bits se reinicia cada ~2,2 años, así que nunca compares dos relojes con `>`
o `<=`: un identificador generado justo después de un reinicio es numéricamente menor que uno generado
justo antes. Usa `wireIdClockDelta(clock, reference)`, la distancia con signo
en el anillo (positiva cuando `clock` está por delante), y `maxWireIdClock(clocks)` para
el más reciente de varios:

```ts
import { maxWireIdClock, wireIdClockDelta } from '@beeuni/sdk/wire-message-id';

const floor = maxWireIdClock([transcriptNewest, deliveredNewest]);
const stale = floor !== null && wireIdClockDelta(clientClock, floor) <= 0n;
```

<a id="server-side-isolation"></a>

#### Aislamiento del lado del servidor

`createBeeuni` almacena su configuración, incluida la función de token, en una
variable global del proceso. Esto es adecuado para una pestaña del navegador, una CLI o un
servidor de un solo inquilino. No es seguro para un servidor Node que gestiona solicitudes
concurrentes de distintos usuarios, porque la última llamada a `createBeeuni` prevalece para
todas las solicitudes en curso. `@beeuni/sdk/server` lo resuelve con aislamiento por
solicitud:

```ts
import { createScopedBeeuni } from '@beeuni/sdk/server';

export async function handler(req: Request) {
  const beeuni = createScopedBeeuni({ backendUrl, getToken: () => tokenFor(req) });
  return beeuni.projects.list();
}
```

`createScopedBeeuni` y `runWithBeeuni` aíslan la configuración por solicitud con
`AsyncLocalStorage` de Node. Nunca importes `@beeuni/sdk/server` desde un paquete de navegador:
incorpora `node:async_hooks` de forma estática.

El `runtime()` de nivel superior de un cliente con ámbito limitado lanza una excepción (resolvería el
sandbox de otro inquilino). Accede al entorno de ejecución de una sesión específica mediante
`beeuni.session(pid, sid).runtime` después de `await s.ensureReady()`.

<a id="deprecated-aliases"></a>

### Alias obsoletos

Unas veinte subrutas antiguas siguen funcionando: `/files`, `/turns`, `/session`, `/auth`,
`/projects-client`, `/api-client`, `/config`, `/event-stream`,
`/opencode-client`, `/platform-client` y más. Cada una reexporta código que el
punto de entrada raíz `@beeuni/sdk` ya exporta. Siguen funcionando para que ningún import breaks, but new code should import from the root. existente

<a id="internal-modules"></a>

### Módulos internos

`@beeuni/sdk/internal/*` contiene los almacenes de zustand que el entorno de ejecución propio de apps/web usa
internamente: estado de sincronización de sesiones, seguimiento del entorno de ejecución activo y gestión de
reconexiones. Esta subruta queda explícitamente fuera de semver. No la importes en
el código del host; los [hooks de React](/docs/es/sdk/react) ya exponen el estado que
necesitas.

<a id="turns"></a>

## Turnos

Funciones simples que agrupan los mensajes de sesión en turnos y clasifican cada
parte de un mensaje. Úsalas para crear un renderizador de chat personalizado en lugar del
de referencia en `apps/web`. Sin React ni DOM: cada exportación es una función
simple o un tipo, y se puede llamar de forma segura desde cualquier host.

```ts
import { classifyPart, classifyTurn, toolInfo, toolViewModel } from '@beeuni/sdk';
```

Importa todas las funciones desde el punto de entrada raíz `@beeuni/sdk`. La
subruta `@beeuni/sdk/turns` sigue funcionando, pero es un alias obsoleto. El código
nuevo debe usar el punto de entrada raíz; consulta [Distribución](#distribution).

<a id="classify-a-part"></a>

### Clasificar una parte

`classifyPart(part)` normaliza uno de los 12 tipos de partes del formato de
transcripción de Beeuni (`beeuni.transcript.v1`, `BEEUNI_TRANSCRIPT_SCHEMA`) en un
`ClassifiedPart`: una unión discriminada por `kind`. Cada variante ya resuelve los
campos que necesita un renderizador: estado de la herramienta, salida JSON analizada y detección de imágenes.

```ts
import { classifyPart, type ClassifiedPart } from '@beeuni/sdk';

for (const part of message.parts) {
  const classified: ClassifiedPart = classifyPart(part);
  switch (classified.kind) {
    case 'text':
      render(classified.text);
      break;
    case 'tool':
      render(classified.tool.title, classified.tool.status);
      break;
  }
}
```

| `kind` | estructura |
| --- | --- |
| `text` | `{ id, text, synthetic }` — omite las partes `synthetic`; marcan el prompt sintético del modo shell |
| `reasoning` | `{ id, text }` |
| `tool` | `{ id, tool: ToolView }` |
| `file` | `{ id, filename?, mime, url, isImage, isPdf }` |
| `subtask` | `{ id, description, agent, prompt, model? }` |
| `patch` | `{ id, hash, files, fileCount }` |
| `snapshot` | `{ id, snapshot }` |
| `agent` | `{ id, name }` |
| `retry` | `{ id, attempt, message, createdAt }` |
| `compaction` | `{ id, auto, overflow, tailStartId? }` |
| `step` | `{ id, phase: 'start' \| 'finish', snapshot?, reason?, cost?, tokens? }` |
| `unknown` | `{ raw }` — un tipo de parte que esta versión del SDK no conoce |

Una parte recibida no reconocida se convierte en `unknown` en tiempo de ejecución en lugar de
lanzar una excepción. Esto permite que un cliente antiguo se comunique con un servidor más reciente.

Una parte de herramienta se clasifica en `ToolView`:

```ts
interface ToolView {
  name: string;
  title: string;
  status: 'pending' | 'running' | 'done' | 'error';
  input?: Record<string, unknown>;
  output?: string;
  error?: string;
  outputParsed?: unknown; // JSON.parse(output) when it parses, capped at 256KB
  outputText?: string;    // the raw output text, always present
  kind?: ToolKind;        // toolKind(name): the action, whatever the harness calls the tool
  files?: ToolFile[];     // the files a file tool read, wrote or changed: path, patch, additions/deletions
  diff?: string;          // a unified diff of the change
  diagnostics?: Record<string, unknown[]>; // language-server diagnostics, keyed by file path
  answers?: string[][];   // the answers to a `question`, one list per question
  childSessionId?: string; // the child session a delegating call (`task`) runs
}
```

`toToolView(part)` crea uno directamente a partir de una parte de herramienta. Lee estos campos
en lugar del `state.metadata` propio de un harness: así, la misma tarjeta muestra un
`edit` de runtime (`filePath`, `filediff`) y un `edit` de pi (`path`, `edits`,
un `patch` unificado).

Algunas herramientas (`web_search`, `image_search`, llamadas a conectores) indican
`state.status: 'completed'` incluso cuando su cuerpo JSON contiene
`success: false`. `classifyPart` lo detecta y establece `ToolView.status` en
`'error'` también en ese caso.

`classifyTurn(message)` clasifica cada parte de un mensaje del asistente y
devuelve un `ClassifiedTurn` con tres campos:

- `parts` — cada parte, clasificada
- `error` — de `message.info.error`, si existe
- `isEmpty` — verdadero cuando el turno no tiene errores ni partes con contenido visible

<a id="tool-metadata"></a>

### Metadatos de herramientas

`toolKind(name)` es la única taxonomía de herramientas: asigna el nombre de herramienta de cualquier harness
(`write` de pi, `oc-apply_patch` de runtime, `pty_spawn` de un plugin) a un
`ToolKind`: `read`, `edit`, `write`, `bash`, `web_search`, `task`,
`question`, … o `other`. Las familias de narración, los modelos de vista, las categorías de `toolInfo`
y el grupo de herramientas de contexto derivan de ella. `inputPath(input)`
lee la ruta de una herramienta de archivos de `filePath`, `file_path` o `path`.

```ts
toolKind('oc-apply_patch'); // 'apply_patch'
toolKind('pty_spawn');      // 'pty'
inputPath({ path: 'app.py' }); // 'app.py'
```

Otras dos funciones de consulta describen una herramienta. No las confundas.

- `toolInfo(name)` — sin iconos, devuelve `{ label, category }`. `classifyPart`
  la usa internamente. `ToolCategory` es `'shell' | 'files' | 'search' |
  'edit' | 'web' | 'task' | 'other'`.
- `getToolInfo(name, input)` — con iconos, devuelve `{ icon, title, subtitle }`
  para la interfaz de referencia de tarjetas de herramientas. El subtítulo procede de la entrada de la herramienta,
  por ejemplo, una ruta de archivo o una consulta de búsqueda.

```ts
toolInfo('bash'); // { label: 'Shell', category: 'shell' }
getToolInfo('write', { filePath: '/workspace/main.go' });
// { icon: 'file-pen', title: 'Write', subtitle: 'main.go /workspace' }
```

Ambas funciones reconocen prefijos de nombres de herramientas, por lo que las familias de herramientas de plugins de Beeuni
(`agent_*`, `session_*`, `task_*`, `trigger_*`, `project_*`, `pty_*`) se resuelven
sin actualizar el registro. Un nombre de herramienta desconocido nunca lanza una excepción: usa
`humanizeToolName(name)` con la categoría `'other'` como alternativa.

<a id="tool-view-models"></a>

### Modelos de vista de herramientas

`toolViewModel(classifiedTool)` asigna una parte de herramienta clasificada a una estructura diseñada
para una familia de herramientas. Una interfaz puede entonces mostrarla de forma específica en lugar de como un
bloque JSON genérico.

```ts
const vm = toolViewModel(classifiedTool);
if (vm.kind === 'shell') {
  render(vm.command, vm.stdout, vm.exitCode);
}
```

| `kind` | estructura | herramientas |
| --- | --- | --- |
| `web-search` | `{ query, results?, answer?, error? }` | `web_search`, `image_search` |
| `shell` | `{ command, stdout?, exitCode? }` | `bash` |
| `file-read` | `{ path, preview? }` | `read` |
| `file-write` | `{ path, preview? }` | `write` |
| `file-edit` | `{ path, diff?: DiffLine[] }` | `edit`, `morph_edit` |
| `search` | `{ pattern, matches?: SearchMatch[] }` | `grep`, `glob` |
| `task` | `{ description, agent? }` | `task` |
| `todo` | `{ items: TodoItem[] }` | `todowrite` |
| `question` | `{ questions: QuestionItem[], answers? }` | `question`, `ask` |
| `generic` | `{ label, inputPretty?, outputPretty? }` | todo lo demás, siempre se puede mostrar de forma segura |

`DiffLine` es `{ type: 'added' | 'removed' | 'unchanged', text }`. Los campos de texto
se limitan para que una salida de herramienta grande nunca impida el renderizado: 4000
caracteres para JSON con formato, 256 KB antes de ejecutar un diff.

<a id="group-messages-into-turns"></a>

### Agrupar mensajes en turnos

Un turno empareja un mensaje del usuario con los mensajes del asistente que lo respondieron.
Es la unidad que una interfaz de chat muestra como un intercambio.

```ts
import { groupMessagesIntoTurns, collectTurnParts, type TurnLike } from '@beeuni/sdk';

const turns: TurnLike[] = groupMessagesIntoTurns(messages);
for (const turn of turns) {
  const parts = collectTurnParts(turn);
}
```

`groupMessagesIntoTurns` vincula cada mensaje del asistente con su mensaje de usuario
padre. Recurre al orden de los mensajes cuando falta el vínculo con el padre. También
asocia un mensaje huérfano del asistente —uno sin padre que precede a
todos los mensajes del usuario— al primer turno, no al último.

Funciones auxiliares relacionadas:

- `findLastTextPart(parts)` — el texto de la respuesta final del turno
- `turnHasSteps(parts)` — verdadero si existe una parte `tool`, `compaction`, `snapshot` o `patch`
- `isShellMode(turn)` / `getShellModePart(turn)` — un turno formado por un prompt sintético que impulsa una llamada a `bash`

<a id="type-guards"></a>

### Guardas de tipo

Acota el tipo de una parte por `type`:

```ts
import { isTextPart, isToolPart, getPartText } from '@beeuni/sdk';

if (isTextPart(part)) {
  // part.type narrowed to 'text'
}
const text = getPartText(part); // works for 'text' and 'reasoning' parts
```

También disponibles: `isReasoningPart`, `isFilePart`, `isAgentPart`,
`isCompactionPart`, `isSnapshotPart`, `isPatchPart`, `isStepPart`
(`step-start` y `step-finish`).

<a id="status-and-errors"></a>

### Estado y errores

```ts
import { getWorkingState, getTurnStatus, formatDuration } from '@beeuni/sdk';

const status = getTurnStatus(parts, childMessages); // "Running commands..."
formatDuration(4300); // "4s" — durations under 1s return ''
```

`getTurnStatus` busca la última línea de estado en las partes de un turno. Cuando la última
parte es una delegación `task` en ejecución, pasa `childMessages` para que el estado muestre
la actividad real del subagente en lugar de una línea genérica «Delegando...».

```ts
import { getTurnError, getChildSessionError, unwrapError } from '@beeuni/sdk';

getTurnError(turn);                    // the first assistant error, unwrapped
getChildSessionError(childMessages);   // newest error in a sub-agent's messages
unwrapError(rawError);                 // normalizes double-JSON and mixed error shapes
```

<a id="cost-and-token-totals"></a>

### Totales de costes y tokens

```ts
import { getTurnCost, getSessionCost, formatCost, formatTokens } from '@beeuni/sdk';

const info = getTurnCost(partsWithMessage, modelPricingLookup);
const sessionCost = getSessionCost(messages, modelPricingLookup);

formatCost(0.0032);  // "$0.003"
formatTokens(12345); // "12k"
```

Ambas funciones leen los totales de costes y tokens de las partes `step-finish`. Cuando una
parte indica cero, estiman el coste a partir de los recuentos de tokens usando un
`ModelPricingLookup`.

Los mensajes de suscripción de ChatGPT/Codex aportan `$0.00`, incluidos los mensajes
históricos con costes de ejecución distintos de cero. El uso de tokens y contexto sigue siendo visible.
Las sesiones mixtas conservan los cargos de los mensajes de API de pago. Los modelos de la API de OpenAI siguen
siendo facturables, incluidos los modelos de API cuyos nombres contienen `codex`.

<a id="child-sessions-and-pending-requests"></a>

### Sesiones hijas y solicitudes pendientes

El árbol de conversaciones de una sesión de proyecto (la raíz y las conversaciones de subagentes
que generó) procede de su lista `runtime_sessions`. Estas
funciones auxiliares lo leen y dan preferencia a los nombres actuales frente a los anteriores a W4:

```ts
import { directSubsessions, projectSessionForRuntimeId, rootRuntimeSession, runtimeSessionsOf } from '@beeuni/sdk';

const root = rootRuntimeSession(projectSession);            // the root conversation id
const children = directSubsessions(projectSession);         // its subagent conversations
const owner = projectSessionForRuntimeId(projectSessions, childId); // the session a conversation belongs to
```

Una llamada a la herramienta `task` delega en una sesión hija. Estas funciones auxiliares conectan un
turno padre con los mensajes propios de esa sesión hija.

```ts
import { getChildSessionId, getChildSessionToolParts } from '@beeuni/sdk';

const childId = getChildSessionId(taskToolPart);
const steps = getChildSessionToolParts(childMessages);
```

Asocia una solicitud pendiente de permiso o de pregunta con su llamada a herramienta y encuentra
qué partes de herramienta ocultar mientras una esté activa:

```ts
import { getPermissionForTool, getHiddenToolParts, isToolPartHidden } from '@beeuni/sdk';

const permission = getPermissionForTool(permissions, callID);
const hidden = getHiddenToolParts(activePermission, activeQuestion);
```

<a id="formatting-and-lists"></a>

### Formato y listas

```ts
import { getFilename, getDirectory, relativizePath, stripAnsi } from '@beeuni/sdk';

getFilename('/workspace/src/main.go');               // "main.go"
getDirectory('/workspace/src/main.go');               // "/workspace/src"
relativizePath('/workspace/src/main.go', '/workspace'); // "src/main.go"
```

Las funciones auxiliares de listas de sesiones operan sobre los mismos datos de sesión:

```ts
import { sortSessions, childMapByParent, allDescendantIds } from '@beeuni/sdk';

sessions.sort(sortSessions(Date.now())); // pins sessions updated in the last 60s
const childMap = childMapByParent(sessions);
const descendants = allDescendantIds(childMap, sessionId);
```

Estado de reintento: `getRetryInfo(sessionStatus)` devuelve `{ attempt, message, next,
details? }` when `status.type === 'retry'`, with `message` limitado a 60
caracteres. `details` conserva un sobre estructurado de la pasarela LLM cuando existe:
el proveedor final, el código de la pasarela, el ID de solicitud, la sugerencia, el estado del servicio ascendente y
los fallos de candidatos ordenados. Cada fallo de candidato contiene `attempt`,
`provider`, `routeModel`, `resolvedModel`, `stage`, un `status` opcional, `code`
y `message`. Los reintentos heredados en texto plano devuelven `details: undefined`.
`getRetryMessage(sessionStatus)` devuelve el mensaje completo sin envoltorio. Cuando
runtime conserva solo ese mensaje, el compuesto de la pasarela sigue incluyendo el
ID de solicitud y el proveedor, el modelo resuelto, el estado HTTP, el código
y el mensaje limitado de cada candidato.

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

### Tipos estructurales

Las funciones de agrupación y estado aceptan tipos estructurales mínimos —
`PartLike`, `MessageInfoLike`, `TurnLike`, `ToolStateLike`,
`SessionStatusLike` — en lugar de los tipos concretos de transcripción.
Tus propias estructuras de mensajes y partes se mantienen sin cambios siempre que coincidan con
los campos requeridos. `classifyPart` y `classifyTurn` son la excepción.
Se tipan con la unión `Part` de Beeuni (`beeuni.transcript.v1`), por lo que su
comprobación de exhaustividad detecta un nuevo tipo de parte durante la compilación.

<a id="distribution"></a>

## Distribución

```sh
npm install @beeuni/sdk
```

El paquete se distribuye como ESM compilado con declaraciones completas de tipos de TypeScript.
`react` y `@tanstack/react-query` son dependencias pares opcionales. Si
usas `@beeuni/sdk/react`, instala ambas.

```ts
import { createBeeuni } from '@beeuni/sdk'; // framework-free core
import { useSession } from '@beeuni/sdk/react'; // optional React layer
import { createScopedBeeuni } from '@beeuni/sdk/server'; // Node and Bun servers
```

<a id="entry-points-and-stability"></a>

### Puntos de entrada y estabilidad

El punto de entrada raíz, `@beeuni/sdk`, es el canónico. Exporta toda la
interfaz independiente de frameworks y se ejecuta en navegadores, Node 18+, Bun y entornos de ejecución
edge.

| Entrada | Nivel | Contrato |
| --- | --- | --- |
| `@beeuni/sdk` | Canónico | Independiente de frameworks. Nunca importa React ni `node:*`. |
| `@beeuni/sdk/react` | Compatible | Hooks de React. La única entrada que importa React. |
| `@beeuni/sdk/server` | Compatible | Aislamiento de la configuración por solicitud para servidores Node y Bun, mediante `node:async_hooks`. Nunca lo incluyas en un bundle para navegador. |
| Subrutas heredadas (`/projects-client`, `/turns`, `/files` y más) | Obsoleto | Siguen funcionando. Cada una reexporta desde la raíz. Importa desde la raíz en su lugar. |
| `@beeuni/sdk/internal/*` | Interno | Solo lo usa la aplicación web de Beeuni. No es una API compatible. |

<a id="cdn-bundles"></a>

### Bundles para CDN

El paquete también incluye dos bundles para navegador, compilados con `tsup`: un bundle ESM
(`dist/beeuni.esm.min.js`) y un global IIFE (`dist/beeuni.global.js`) que
define `window.Beeuni`.

```html
<script src="https://unpkg.com/@beeuni/sdk/dist/beeuni.global.js"></script>
<script>
  const beeuni = Beeuni.createBeeuni({
    backendUrl: 'https://api.beeuni.ai/v1',
    getToken: async () => KEY,
  });
</script>
```

`window.Beeuni` expone el mismo punto de entrada raíz que la importación de npm:
`Beeuni.createBeeuni`, `Beeuni.classifyTurn`, `Beeuni.ApiError`.

<a id="see-also"></a>

## Consulta también

- [SDK](/docs/es/sdk) — instalación y tu primera sesión.
- [Sesiones](/docs/es/sdk/sessions) — ciclo de vida, streaming y gestión de errores.
- [Hooks de React](/docs/es/sdk/react) — la capa reactiva construida sobre estos módulos.


<a id="agent-repository-access"></a>

### Acceso del agente al repositorio

La configuración del agente acepta `repository_access?: boolean` (valor predeterminado: `true`).
Establece `false` para ejecutar nuevas sesiones sin el repositorio del proyecto ni acceso a la API del repositorio.
Los permisos de Git, secretos, conectores y herramientas siguen siendo independientes. Las sesiones existentes conservan su política guardada.
`AgentConfigBlock.workspace` está obsoleto. El SDK asigna los valores heredados `branch`/`runtime` al campo booleano.
Una escritura heredada de `read` requiere elegir explícitamente `repository_access`; no habilita el acceso de solo lectura al repositorio.


<a id="provider-and-model-access"></a>

## Acceso a proveedores y modelos

Lee `beeuni.projects.modelAccess(projectId)`. Cambia un destino con `beeuni.projects.setModelAccess(projectId, { target: 'provider' | 'model', id, enabled })`.

Usa el ID de proveedor `beeuni` para Beeuni Managed Models, o un ID de proveedor del catálogo para BYOK, Codex y proveedores personalizados. Usa el ID completo del protocolo del gateway para un modelo. La desactivación del proveedor abarca los modelos futuros y conserva las preferencias de cada modelo y las credenciales guardadas.

El modelo predeterminado actual y su proveedor requieren un sustituto antes de desactivarlos (`409 cannot_disable_default`). Las solicitudes directas al gateway dirigidas a destinos desactivados fallan con `model_disabled` o `provider_disabled`. Las solicitudes al proveedor que ya están en curso finalizan normalmente. El indicador `enforced` de la respuesta es false cuando el proyecto usa runtime nativo en lugar del gateway.

Los consumidores de React usan `useModelAccess(projectId)` de `@beeuni/sdk/react`. Su mutación `setEnabled(change)` actualiza el catálogo de administración y el selector de sesiones.
