En esta sección
Guías / Mostrar un run en vivo en tu UI

Mostrar un run en vivo en tu UI

Pasar el stream del Met desde tu backend hasta el navegador, palabra por palabra, sin exponer la key y sin perder el resultado si el stream se corta.

Actualizada el Ver .md
StackNode · Express · SSE · navegador
Endpointsruns.stream · runs.retrieve

Esperar diez segundos mirando un spinner se siente roto aunque no lo esté. Meteor entrega el run como Server-Sent Events, y esta receta lo lleva hasta el navegador.

El punto que no se puede saltar: la key es un secreto de servidor. El navegador nunca habla con la API de Meteor; habla con tu backend, y tu backend retransmite.

Antes de empezar

Tu key necesita runs:execute.

1. El relay en tu backend

import express from 'express';
import Met from '@meteor.ia/sdk';

const met = new Met(process.env.MET_API_KEY!, {
  workspaceId: Number(process.env.MET_WORKSPACE_ID),
});

const app = express();

app.get('/api/preguntar', async (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  res.flushHeaders();

  try {
    for await (const ev of met.runs.stream({ input: String(req.query.q) })) {
      res.write(`event: ${ev.type}\ndata: ${JSON.stringify(ev.data)}\n\n`);
    }
  } catch (err) {
    res.write(`event: error\ndata: ${JSON.stringify({ message: 'se cortó' })}\n\n`);
  } finally {
    res.end();
  }
});

app.listen(3000);

El SDK te entrega objetos { type, data } ya parseados. El stream emite un conjunto cerrado de eventos:

eventocuándo
run.startedel run arrancó
run.stepun paso del Met, en versión curada
run.outputun fragmento de la respuesta
run.completedterminó bien
run.failedterminó con error

2. El navegador

<p id="respuesta"></p>
<script>
  const es = new EventSource('/api/preguntar?q=' + encodeURIComponent(pregunta));
  const destino = document.getElementById('respuesta');

  es.addEventListener('run.output', (e) => {
    destino.textContent += JSON.parse(e.data);   // llega por fragmentos
  });
  es.addEventListener('run.completed', () => es.close());
  es.addEventListener('run.failed', () => { destino.textContent = 'No pude responder.'; es.close(); });
</script>

EventSource reconecta solo cuando se cae la conexión, y ese comportamiento aquí juega en contra: reconectar a /api/preguntar vuelve a ejecutar el Met, con su costo. Por eso se cierra explícitamente al terminar.

3. No pierdas el resultado si el stream se corta

Un stream cortado a la mitad no se reanuda de forma transparente. Si lo que estás mostrando también tiene que quedar guardado, no dependas del stream para eso: el run existe en la API con o sin stream.

const run = await met.runs.retrieve(runId);   // la fuente de verdad, sin apuro

El patrón para lógica que importa es usar el stream solo para pintar y confirmar contra retrieve() cuando el usuario ya se fue. Si nunca guardas el resultado, un usuario que cierra la pestaña a los ocho segundos deja un run que te cobraron y nadie leyó.

Verlo desde la terminal

curl -N https://api.met.meteor.com.co/api/v1/workspaces/7/runs \
  -H "Authorization: Bearer $MET_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{"input":"Hola","stream":true}'

-N desactiva el buffering: sin él ves todo junto al final y parece que el streaming no funciona.

Y después