Saltar al contenido principal

Conversaciones y streaming

Conversaciones

const conversation = await client.createConversation({
title: 'Soporte de checkout',
idempotencyKey: 'checkout-support-001',
});

const { conversations } = await client.listConversations();

const { messages, outputModules } = await client.getConversationMessages(
conversation.conversationRef,
);

Pasa un idempotencyKey a createConversation() si la llamada puede reintentarse: un reintento con la misma clave devuelve la conversación existente en lugar de crear un duplicado.

Las conversaciones se direccionan siempre por conversationRef. Ver ExternalAgentConversation.

Transmitir un turno

const stream = await client.streamMessage({
conversationRef: conversation.conversationRef,
content: '¿Por qué se rechazó mi tarjeta?',
});

for await (const event of stream) {
switch (event.type) {
case 'message:delta':
appendToBubble(event.delta);
break;
case 'message:complete':
finalize(event.content, event.usage);
break;
case 'error':
showError(event.message, event.retryable);
break;
}
}

streamMessage() devuelve un RuntimeEventStream, un AsyncIterable de eventos públicos normalizados. Además de content y conversationRef, admite streamProfile, outputModuleTypes, providerGroundingDisplay, hostContext, fileRefs, idempotencyKey y signal. Tipo completo: StreamMessageInput.

Cancelar

const controller = new AbortController();
const stream = await client.streamMessage({
conversationRef,
content,
signal: controller.signal,
});

stopButton.onclick = () => controller.abort();

Un stream cancelado se expone como un RuntimeSdkError con código aborted; una conexión que se corta a mitad de turno se expone como stream_interrupted.

Tipos de evento

RuntimeStreamEvent es una unión discriminada por type. Discrimínala de forma exhaustiva, y mantén una rama default que ignore lo que no reconozca: los tipos de evento nuevos son aditivos.

EventoContiene
message:startmessageRef, role
message:deltadelta — concaténalo, no lo reemplaces
message:completecontent, usage, references, files, artifacts, outputModules
tool:startname, displayName, inputPreview
tool:resultoutput, references
tool:errorcode, message
interaction:requestedinteraction — el agente espera al usuario
interaction:resolvedinteractionRef, response
approval:requestedapprovalRef, title, description
approval:resolvedapprovalRef, decision
artifact:createdartifact
runtime:statusstatus — proyección de progreso
errorcode, message, retryable

Recolectar en lugar de transmitir

Cuando no necesitas renderizado token a token, reduce el stream a un mensaje:

import { collectAssistantMessage } from '@agentshelf/external-agents-sdk';

const stream = await client.streamMessage({ conversationRef, content });
const collected = await collectAssistantMessage(stream);

console.log(collected.message.content, collected.usage);

collectAssistantMessage devuelve el message ensamblado más los usage, references, files y artifacts que haya producido el turno. Sigue consumiendo todo el stream, así que la cancelación con signal funciona igual.

Interacciones

Cuando el agente necesita datos a mitad de turno emite interaction:requested y espera. Muestra la interacción y después resuélvela:

for await (const event of stream) {
if (event.type === 'interaction:requested') {
const answer = await promptUser(event.interaction);

await client.respondToInteraction({
conversationRef,
interactionRef: event.interaction.interactionRef,
response: answer,
idempotencyKey: `interaction-${event.interaction.interactionRef}`,
});
}
}

response es un objeto JSON con datos escalares públicos. La resolución se confirma con un evento interaction:resolved posterior.

Las interacciones requieren capabilities.interactions; las aprobaciones requieren capabilities.approvals. Comprueba la política antes de mostrar cualquiera de las dos — ver Políticas y capacidades.

Adjuntos

streamMessage() acepta fileRefs, que referencian archivos que ya has subido con uploadFile(). Todavía no existe un contrato tipado de adjuntos por turno más allá de pasar esas refs públicas.