# SDK del chat

Publica, lee, busca y organiza canales del Chat desde los SDK de JavaScript y Python, y verifica la firma de los avisos que recibes.

Los SDK de JavaScript (`@utilia-os/sdk-js`) y Python (`utilia-sdk`) tienen `sdk.chat`, con lo mismo en los dos lenguajes: publicar, leer, organizar canales y, en `sdk.chat.agents`, gestionar agentes de IA. La verificación de los avisos está en `sdk.webhooks`, común a todos los avisos de UTILIA OS. Esta página usa la versión actual de los dos SDK, la 4.1.0.

Necesitas una aplicación externa con su clave de API y los scopes de lo que vayas a usar (están en [Scopes y acceso por canal](https://os.utilia.ai/docs/developers/chat-platform/scopes-and-access)).

## Métodos

| JavaScript | Python | Qué hace | Scope |
| - | - | - | - |
| `sendMessage(channelId, { content, parentMessageId?, metadata? })` | `send_message(channel_id, SendChatMessageInput(...))` | Publica un mensaje. | `chat:messages:send` |
| `listChannels({ limit?, cursor? })` | `list_channels(limit=, cursor=)` | Lista los canales con acceso. | `chat:channels:view` |
| `getChannel(channelId)` | `get_channel(channel_id)` | Detalle de un canal. | `chat:channels:view` |
| `listMembers(channelId)` | `list_members(channel_id)` | Miembros de un canal. | `chat:channels:view` |
| `listMessages(channelId, { limit?, cursor?, search? })` | `list_messages(channel_id, limit=, cursor=, search=)` | Mensajes visibles. | `chat:messages:view` |
| `getMessage(messageId)` | `get_message(message_id)` | Detalle de un mensaje. | `chat:messages:view` |
| `createChannel({ name, description?, isDiscoverable? })` | `create_channel(CreateChatChannelInput(...))` | Crea un canal. | `chat:channels:create` |
| `updateChannel(channelId, { name?, description? })` | `update_channel(channel_id, UpdateChatChannelInput(...))` | Edita un canal. | `chat:channels:manage` |
| `archiveChannel(channelId)` | `archive_channel(channel_id)` | Archiva un canal. | `chat:channels:manage` |
| `addMember(channelId, { userId })` | `add_member(channel_id, AddChatMemberInput(...))` | Añade a una persona como `MEMBER`. | `chat:members:manage` |
| `removeMember(channelId, userId)` | `remove_member(channel_id, user_id)` | Retira a una persona. | `chat:members:manage` |

Los agentes de IA (`sdk.chat.agents`: `install`, `list`, `update`, `pause`, `resume` y `remove`) tienen su propia página: [Gestionar agentes de IA](https://os.utilia.ai/docs/developers/chat-platform/managed-agents).

## Instalar y configurar

### JavaScript / TypeScript

```bash
npm install @utilia-os/sdk-js
```

```typescript
import { UtiliaSDK } from '@utilia-os/sdk-js';

const sdk = new UtiliaSDK({
  baseURL: 'https://os.utilia.ai/api',
  apiKey: process.env.UTILIA_API_KEY,
});
```

### Python

```bash
pip install utilia-sdk
```

```python
import os
from utilia_sdk import UtiliaSDKSync

# Cliente síncrono. El asíncrono es UtiliaSDK, usado con «async with».
sdk = UtiliaSDKSync(
    base_url="https://os.utilia.ai/api",
    api_key=os.environ["UTILIA_API_KEY"],
)
```

## Leer canales y mensajes

### JavaScript / TypeScript

```typescript
// Canales a los que la app tiene acceso concedido.
const canales = await sdk.chat.listChannels({ limit: 20 });
for (const canal of canales.data) {
  console.log(canal.id, canal.name);
}

// Miembros de un canal.
const miembros = await sdk.chat.listMembers('cnv_8f3a21');

// Historial visible, del más reciente al más antiguo (cursor).
let cursor: string | null = null;
do {
  const pagina = await sdk.chat.listMessages('cnv_8f3a21', {
    limit: 50,
    cursor: cursor ?? undefined,
  });
  for (const mensaje of pagina.data) {
    console.log(mensaje.author.displayName, mensaje.content);
  }
  cursor = pagina.pagination.nextCursor;
} while (cursor);

// Buscar por texto dentro de un canal.
const encontrados = await sdk.chat.listMessages('cnv_8f3a21', {
  search: 'temperatura',
});

// Detalle de un mensaje concreto (útil tras recibir un webhook).
const mensaje = await sdk.chat.getMessage('msg_a1b2c3d4');
```

### Python

```python
# Canales a los que la app tiene acceso concedido.
canales = sdk.chat.list_channels(limit=20)
for canal in canales.data:
    print(canal.id, canal.name)

# Miembros de un canal.
miembros = sdk.chat.list_members("cnv_8f3a21")

# Historial visible, del más reciente al más antiguo (cursor).
cursor = None
while True:
    pagina = sdk.chat.list_messages("cnv_8f3a21", limit=50, cursor=cursor)
    for mensaje in pagina.data:
        print(mensaje.author.display_name, mensaje.content)
    cursor = pagina.pagination.next_cursor
    if not cursor:
        break

# Buscar por texto dentro de un canal.
encontrados = sdk.chat.list_messages("cnv_8f3a21", search="temperatura")

# Detalle de un mensaje concreto (útil tras recibir un webhook).
mensaje = sdk.chat.get_message("msg_a1b2c3d4")
```

## Organizar canales

Estos métodos cumplen las mismas reglas que la API: el scope autoriza la acción, la concesión en el canal autoriza el lugar, y tu aplicación nunca puede más que la persona que le dio acceso. Sin el scope, la respuesta es 403; sin concesión activa en el canal, 404. Los detalles están en [Crear canales y gestionar miembros](https://os.utilia.ai/docs/developers/chat-platform/channel-endpoints).

### JavaScript / TypeScript

```typescript
// Crear un canal como la app (queda como creadora/participante).
const canal = await sdk.chat.createChannel({
  name: 'incidencias',
  description: 'Alertas del termómetro',
});

// Editar el nombre y/o la descripción de un canal concedido.
await sdk.chat.updateChannel(canal.id, { name: 'incidencias-graves' });

// Añadir un usuario interno (siempre con rol MEMBER).
await sdk.chat.addMember(canal.id, { userId: 'usr_7a1b' });

// Retirar a un usuario interno.
await sdk.chat.removeMember(canal.id, 'usr_7a1b'); // -> { success: true }

// Archivar el canal cuando ya no se use.
await sdk.chat.archiveChannel(canal.id);
```

### Python

```python
from utilia_sdk import (
    AddChatMemberInput,
    CreateChatChannelInput,
    UpdateChatChannelInput,
)

# Crear un canal como la app (queda como creadora/participante).
canal = sdk.chat.create_channel(
    CreateChatChannelInput(
        name="incidencias", description="Alertas del termómetro"
    )
)

# Editar el nombre y/o la descripción de un canal concedido.
sdk.chat.update_channel(
    canal.id, UpdateChatChannelInput(name="incidencias-graves")
)

# Añadir un usuario interno (siempre con rol MEMBER).
sdk.chat.add_member(canal.id, AddChatMemberInput(user_id="usr_7a1b"))

# Retirar a un usuario interno.
sdk.chat.remove_member(canal.id, "usr_7a1b")  # -> ChatActionResult(success=True)

# Archivar el canal cuando ya no se use.
sdk.chat.archive_channel(canal.id)
```

## Verificar la firma de un aviso

`sdk.webhooks.verify` comprueba la firma de un aviso entrante sin llamadas de red: recalcula el HMAC, lo compara en tiempo constante y rechaza los avisos con más de 300 segundos de diferencia. Si todo cuadra, devuelve el aviso; si no, lanza un error. Lee las cabeceras directamente del objeto de cabeceras de tu servidor.

Pásale siempre el cuerpo tal como llega, como texto. Si tu servidor ya lo ha convertido a JSON y lo vuelves a serializar, la firma no coincidirá: configúralo para conservar el cuerpo original.

### JavaScript / TypeScript

```typescript
const aviso = sdk.webhooks.verify(cuerpo, req.headers, process.env.UTILIA_WEBHOOK_SECRET!);

console.log(aviso.event); // 'CHAT_MESSAGE_CREATED'
```

### Python

```python
aviso = sdk.webhooks.verify(cuerpo, request.headers, os.environ["UTILIA_WEBHOOK_SECRET"])

print(aviso["event"])  # 'CHAT_MESSAGE_CREATED'
```

El nombre del evento va en mayúsculas con guiones bajos (`CHAT_MESSAGE_CREATED`), igual que en la cabecera `X-Utilia-Event`. La lista completa está en [Webhooks de chat](https://os.utilia.ai/docs/developers/chat-platform/webhooks).

> **Atención: sdk.chat.webhooks.verify está en desuso**
>
> Hasta la versión 3.0.0 la verificación se hacía con `sdk.chat.webhooks.verify(cuerpo, { signature, timestamp, secret })`. Sigue funcionando, pero está en desuso: cámbialo por `sdk.webhooks.verify(cuerpo, cabeceras, secreto)`, que vale para todos los avisos de UTILIA OS.

## Siguiente paso

El [ejemplo del termómetro](https://os.utilia.ai/docs/developers/chat-platform/example-thermometer) usa estos métodos de principio a fin: publicar, recibir el aviso, verificarlo y contestar.
