Endpoint principal para interactuar con el agente. Usa Server-Sent Events (SSE) para enviar la respuesta en tiempo real, token por token.
Request
GET https://api.thaliq.com/api/agent/stream?q={mensaje}
Query Parameters
| Parametro | Requerido | Descripcion |
|---|
q | Si | El mensaje del usuario (URL-encoded) |
conversationId | No | ID de conversacion existente. Si se omite o expiro, el backend crea una nueva. Valores reservados (pending, new, null, undefined) son rechazados |
agentId | No | Override del agente. Si se omite, se usa el agente bindeado a la API key (o el default del tenant si no hay binding) |
agentType | No | 'general' por defecto |
channel | No | 'platform' | 'widget' | 'sdk' | 'studio' | 'whatsapp' | 'telegram' | 'slack'. Afecta filtrado en stats e inbox |
actionResponse | No | Respuesta a una accion HITL pendiente (JSON URL-encoded) |
Ver Autenticacion para el detalle completo. Headers comunes:
| Header | Requerido | Descripcion |
|---|
X-API-Key | Si | API Key del tenant |
X-Integration-Type | No | widget o sdk |
X-User-Id | No | ID del usuario final (tracking) |
X-Participant-Id | No | Fingerprint del visitante anonimo |
X-MCP-Tokens | No | JSON {"<serverId>":"<token>"} |
Authorization | No | Bearer <jwt> (passthrough MCP) |
Eventos SSE
La respuesta es un stream de eventos SSE. Cada evento tiene un event type y data JSON:
event: meta
data: {"conversationId":"conv_abc123"}
event: status
data: {"message":"Pensando..."}
event: content.delta
data: {"delta":"Nuestro "}
event: content.delta
data: {"delta":"horario es "}
event: content.delta
data: {"delta":"de 9am a 6pm."}
event: response.completed
data: {"conversationId":"conv_abc123"}
Tipos de eventos
| Evento | Descripcion | Data |
|---|
meta | Metadata inicial de la conversacion (se emite tras resolver/crear el thread). Antes de este evento, no consideres que tenes un conversationId valido | { conversationId } |
keepalive | Heartbeat cada 20s para evitar que proxies/CDNs corten la conexion idle | { ts } |
status | Estado intermedio del agente (“Pensando…”, “Buscando en documentos…”) | { text } |
content.delta | Fragmento de texto de la respuesta. Se emiten muchos en orden — concatenalos | { delta } |
tool.start | El agente comenzo a ejecutar una tool | { tool } |
tool.end | La tool termino de ejecutarse | { tool, success, durationMs } |
rag.context | Chunks RAG recuperados (debug — solo cuando hay docs subidos) | { documentsUsed, totalChunks, queryUsed, tokenCount } |
action | Accion interactiva pendiente (Human-in-the-Loop). El stream se cierra despues de este evento — para continuar mandar actionResponse en un nuevo request | { type, instructionId, message, options?, fields? } |
handoff | Escalacion a un humano (la conversacion entra al inbox) | { message, agentName?, reason? } |
response.completed | Stream completado. Util para capturar metadata final (messageId para feedback) | { message, metadata: { messageId, model, generatedAt }, promptTokens, completionTokens, insights? } |
error | Error durante el procesamiento. El stream se cierra | { message } |
Ejemplo con curl
curl -N -H "X-API-Key: tq_live_xxx" \
"https://api.thaliq.com/api/agent/stream?q=Hola%20necesito%20ayuda"
Ejemplo con JavaScript
const params = new URLSearchParams({ q: 'Hola, necesito ayuda' });
const response = await fetch(
`https://api.thaliq.com/api/agent/stream?${params}`,
{
headers: {
'X-API-Key': 'tq_live_xxx',
},
}
);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// Procesar eventos separados por doble newline
let idx;
while ((idx = buffer.indexOf('\n\n')) !== -1) {
const rawEvent = buffer.slice(0, idx);
buffer = buffer.slice(idx + 2);
let eventType = 'message';
const dataLines = [];
for (const line of rawEvent.split('\n')) {
if (line.startsWith('event:')) eventType = line.slice(6).trim();
else if (line.startsWith('data:')) dataLines.push(line.slice(5).trim());
}
const data = JSON.parse(dataLines.join('\n'));
switch (eventType) {
case 'content.delta':
process.stdout.write(data.delta); // Mostrar texto progresivamente
break;
case 'tool.start':
console.log(`\n[Tool: ${data.tool}]`);
break;
case 'response.completed':
console.log('\n--- Fin ---');
break;
}
}
}
Ejemplo con EventSource (alternativa)
const params = new URLSearchParams({ q: 'Hola' });
const es = new EventSource(
`https://api.thaliq.com/api/agent/stream?${params}&apiKey=tq_live_xxx`
);
es.addEventListener('content.delta', (e) => {
const data = JSON.parse(e.data);
document.getElementById('response').textContent += data.delta;
});
es.addEventListener('response.completed', () => {
es.close();
});
es.addEventListener('error', () => {
es.close();
});
EventSource no soporta custom headers. Para enviar la API Key como header (recomendado), usa fetch con ReadableStream como en el ejemplo anterior.
Respondiendo a acciones interactivas
Cuando recibes un evento action, el stream se cierra. Para responder, envias un nuevo request con el parametro actionResponse:
// Respuesta a un confirm
const actionResponse = JSON.stringify({
instructionId: 'inst_abc123',
accepted: true,
});
const params = new URLSearchParams({
q: 'Quiero agendar una cita', // El mensaje original
actionResponse,
});
const response = await fetch(
`https://api.thaliq.com/api/agent/stream?${params}`,
{ headers: { 'X-API-Key': 'tq_live_xxx' } }
);
Tipos de actionResponse
Consent / Confirm:
{ "instructionId": "inst_xxx", "accepted": true }
Select:
{ "instructionId": "inst_xxx", "selectedValue": "opcion_1" }
Form:
{ "instructionId": "inst_xxx", "formValues": { "nombre": "Juan", "email": "juan@email.com" } }