Tema
Canales
Un canal es por dónde entra una conversación a un agente. Cada agente tiene exactamente dos, creados automáticamente al crearlo:
| Canal | Cómo lo llama el panel | Qué es | Paquete |
|---|---|---|---|
widget | Chat embebible en cualquier web | El chat flotante que embebes con <script> | @nykk/ur-agent-widget |
js_sdk | Integración a medida con tu propia interfaz | Tu propia UI, construida sobre el SDK | @nykk/ur-agent-client |
Los identificadores widget y js_sdk son los que verás en la URL, en los filtros de conversaciones y en esta documentación, pero el panel no los muestra: allí cada canal se reconoce por su nombre y por el paquete npm que aparece debajo.
No se crean ni se borran: solo se editan. Están en la ficha del agente, en la sección Canales del menú lateral del formulario — aparece una vez el agente está guardado, al crearlo todavía no. Cada fila lleva a la izquierda el interruptor del canal, que se guarda con el botón Guardar del agente, y se abre en su propio panel para lo demás. Si admite cualquier origen, la fila lo marca en amarillo. La del widget tiene además un ▶ para probarlo en la propia página del panel.
Qué se configura en cada uno
Habilitado. Si el canal está deshabilitado, las llamadas por esa vía devuelven 404 — el mismo código que un agente inexistente, a propósito, para no revelar qué agentes existen.
Orígenes permitidos. O bien * (cualquiera) o bien una lista explícita de orígenes (https://tutienda.com). Se comprueba contra la cabecera Origin de la petición.
Cada origen es esquema, dominio y, si no es el de por defecto, puerto: sin ruta, sin ? y sin comodines. https://tienda.com y https://www.tienda.com son dos orígenes distintos; si tu web responde en los dos, pon los dos. Se guardan como los manda el navegador, así que https://Tienda.com/ o https://tienda.com:443 quedan como https://tienda.com. Lo que no es un origen (tienda.com, https://tienda.com/chat, https://*.tienda.com) se rechaza al guardar.
Si la petición trae un Origin que no está en la lista, la respuesta es 403 con {"error":"Origin not allowed"} — distinto del 404 del canal desactivado, para que puedas distinguir «el dominio está mal configurado» de «el canal está apagado». Una página abierta desde el disco (file://) o un iframe con sandbox mandan Origin: null, que no casa con ninguna lista. GET /embed/:agente/config (la configuración que el cliente se descarga al arrancar) es la excepción: solo exige que el canal esté activado, no comprueba el origen.
Apariencia (solo widget). El título de la cabecera del chat (máximo 100 caracteres) y los colores de marca. El widget la pide sola al arrancar con GET /embed/:agente/config?channel=widget, para que el <script> embebido no tenga que repetirla. Si dejas el título vacío, «Asistente virtual». Un data-title o data-color* explícito en el snippet sigue ganando. Ver Widget.
js_sdk no tiene apariencia: la interfaz es tuya, y el título y los colores van en tu código (ver SDK). Su /config?channel=js_sdk trae style vacío y sin title.
Sin ?channel=, /config devuelve la del widget: es lo que piden los clientes anteriores a este parámetro.
/config trae además los límites de conversación del agente, si los tiene: maxTurns (máximo de turnos por conversación) y conversationTtlHours (horas sin actividad para olvidarla). El widget los aplica solo; con el SDK, ver Fin de conversación. Y si el agente identifica a su usuario, authMode (optional o required): /config no pide token, pero el resto de rutas sí.
La lista de orígenes NO es autenticación
Solo evita el copia-pega del snippet en un sitio ajeno. Un cliente que no sea un navegador puede omitir la cabecera Origin o falsificarla — y de hecho, si la cabecera no viene, la petición se acepta, porque las llamadas servidor a servidor no la envían.
El freno real al abuso es el rate limit por IP y los límites por agente. No pongas detrás de un canal público nada que no puedas enseñar a cualquiera. Lo que solo debe ver un usuario concreto necesita que el agente lo identifique.
widget y js_sdk son el mismo protocolo
Hablan con los mismos endpoints, devuelven los mismos eventos y comparten el límite por IP del agente (180 peticiones por minuto por defecto). Se diferencian en tres cosas:
- Se habilitan y se restringen por separado. Puedes tener el widget abierto a tu dominio público y el SDK apagado, o al revés.
- Sus conversaciones son hilos distintos. El hilo se identifica por la terna
(agente, subjectId, canal)(y, con un usuario identificado, también por el usuario), así que el mismo usuario enwidgety enjs_sdktiene dos conversaciones separadas, cada una con su memoria. - Los límites por agente se cuentan por separado. Los mensajes por hora y la ventana de ráfaga llevan el canal en la clave del contador: la misma persona tiene una cuota en
widgety otra distinta enjs_sdk.
El segundo punto importa si migras de una vía a otra: la conversación no viaja. Un usuario que venía hablando por el widget empezará de cero al pasar al SDK.
El SDK usa js_sdk por defecto
@nykk/ur-agent-client manda por el canal js_sdk salvo que le digas otra cosa. En el panel es el que se llama Integración a medida con tu propia interfaz (@nykk/ur-agent-client): ese es el que tienes que tener activado, no el Chat embebible. También si lo que construyes con el SDK es un widget propio: widget es el canal del <ur-agent-chat> de @nykk/ur-agent-widget.
Solo hay tres valores de canal: widget, js_sdk y demo. Cualquier otro (js-sdk, sdk…) se responde con 400. Si no mandas ninguno, es widget.
El canal demo
En el panel, el botón Probar agente abre un chat que va por un tercer canal, demo. No tiene fila de configuración, no se restringe por origen, y es el único que recibe información de diagnóstico: qué tools se han llamado y cuántos tokens ha costado el turno.
Justamente por eso solo responde a quien tenga una sesión del panel abierta con acceso a ese agente (un administrador, o un usuario cuyos grupos lo incluyan): desde fuera del panel, pedir ese canal devuelve 404. No lo uses en una integración.
Eso explica una asimetría que confunde al integrar: onToolEvent y onUsage del SDK no disparan en una integración normal. Esos frames solo se emiten cuando la petición entra por demo; el widget y el SDK entran por widget/js_sdk, así que tu UI no debe contar con ellos ni tratarlos como parte del contrato.
La otra asimetría va justo al revés: el contexto de cliente sí llega a las tools por widget y js_sdk, pero se descarta en demo — no intentes probarlo desde «Probar agente».
Con un agente que identifica a su usuario, demo no lee Authorization: en su lugar, el chat de pruebas deja elegir una identidad de prueba (un token, verificado contra el emisor o no, o unos claims escritos a mano), que viaja en la cabecera X-Ur-Test-Identity y solo se atiende en este canal. Si el token no pasa, el chat dice por qué, cosa que widget y js_sdk nunca hacen. La conversación sigue siendo la tuya, la de tu sesión del panel: una identidad de prueba nunca abre ni lee la de un usuario real.