> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thaliq.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Human-in-the-Loop

> Como manejar acciones interactivas (consent, confirm, select, form) en el SDK

Cuando un agente necesita confirmacion del usuario antes de ejecutar una accion sensible, emite un evento de **accion HITL** (Human-in-the-Loop). El SDK te da control completo para manejar estas acciones programaticamente.

## Tipos de accion

| Tipo      | Descripcion                 | Ejemplo                                          |
| --------- | --------------------------- | ------------------------------------------------ |
| `consent` | Checkbox + Aceptar/Rechazar | "Acepto los terminos para procesar mi solicitud" |
| `confirm` | Si / No                     | "¿Confirmas que quieres agendar la cita?"        |
| `select`  | Elegir entre opciones       | "¿Que tipo de consulta necesitas?"               |
| `form`    | Formulario con campos       | "Completa tus datos para continuar"              |

## Flujo basico

```mermaid theme={null}
sequenceDiagram
    participant App as Tu App
    participant SDK as Thaliq SDK
    participant API as Thaliq API

    App->>SDK: stream('Transfiere $500')
    SDK->>API: SSE /api/agent/stream
    API-->>SDK: content.delta (texto parcial)
    API-->>SDK: action (confirm)
    SDK-->>App: event.type === 'action'
    Note over App: Muestra UI de confirmacion
    App->>SDK: respondToAction({ accepted: true })
    SDK->>API: SSE /api/agent/stream + actionResponse
    API-->>SDK: content.delta + tool.start + tool.end
    API-->>SDK: response.completed
    SDK-->>App: Respuesta final
```

1. Envias un mensaje via `stream()`
2. El agente detecta que necesita confirmacion
3. El stream emite un evento `action` y se pausa
4. Tu app muestra la accion al usuario
5. El usuario responde
6. Envias la respuesta con `respondToAction()` para reanudar

```typescript theme={null}
import { Thaliq, ActionResponseBuilder } from '@thaliq/sdk';

const thaliq = new Thaliq({ apiKey: 'tq_live_xxx' });

// 1. Enviar mensaje
const stream = thaliq.agent.stream('Transfiere $500 a Juan');

// 2. Consumir eventos
for await (const event of stream) {
  if (event.type === 'content.delta') {
    process.stdout.write(event.delta);
  }
}

// 3. Verificar si hay accion pendiente
if (stream.pendingAction) {
  console.log(stream.status);                    // 'awaiting_action'
  console.log(stream.pendingAction.type);        // 'confirm'
  console.log(stream.pendingAction.message);     // '¿Confirmas la transferencia?'

  // 4. Responder a la accion
  const resumed = thaliq.agent.respondToAction({
    conversationId: stream.conversationId!,
    originalMessage: 'Transfiere $500 a Juan',
    actionResponse: ActionResponseBuilder.accept(stream.pendingAction.instructionId),
  });

  // 5. Consumir la respuesta reanudada
  const response = await resumed.finalResponse();
  console.log(response.message);
}
```

## ActionResponseBuilder

Helpers para construir respuestas a cada tipo de accion:

```typescript theme={null}
import { ActionResponseBuilder } from '@thaliq/sdk';

// Consent / Confirm — aceptar
ActionResponseBuilder.accept(instructionId);

// Consent / Confirm — rechazar
ActionResponseBuilder.reject(instructionId);

// Select — elegir una opcion
ActionResponseBuilder.select(instructionId, 'wire');

// Form — enviar valores
ActionResponseBuilder.submitForm(instructionId, {
  amount: '500',
  currency: 'USD',
  recipient: 'Juan',
});
```

## Patron completo (todos los tipos)

```typescript theme={null}
const stream = thaliq.agent.stream('Configura la transferencia');
for await (const event of stream) {
  if (event.type === 'content.delta') process.stdout.write(event.delta);
}

if (stream.pendingAction) {
  const action = stream.pendingAction;
  let actionResponse;

  switch (action.type) {
    case 'consent':
    case 'confirm':
      const accepted = await askUser(action.message);
      actionResponse = accepted
        ? ActionResponseBuilder.accept(action.instructionId)
        : ActionResponseBuilder.reject(action.instructionId);
      break;

    case 'select':
      // action.options = [{ label: 'Wire', value: 'wire' }, ...]
      const selected = await showOptions(action.options!);
      actionResponse = ActionResponseBuilder.select(action.instructionId, selected);
      break;

    case 'form':
      // action.fields = [{ name: 'amount', label: 'Monto', type: 'number' }, ...]
      const values = await showForm(action.fields!);
      actionResponse = ActionResponseBuilder.submitForm(action.instructionId, values);
      break;
  }

  const resumed = thaliq.agent.respondToAction({
    conversationId: stream.conversationId!,
    originalMessage: 'Configura la transferencia',
    actionResponse,
  });

  // Usa finalResponse() para obtener texto + metadata en una sola lectura
  const response = await resumed.finalResponse();
  console.log(response.message);
  console.log(response.insights);
}
```

<Warning>
  **Consumo unico del stream.** El stream retornado por `respondToAction()` sigue la misma regla: solo puede ser consumido una vez. Usa `finalResponse()` para obtener tanto el texto como la metadata en una sola lectura.

  ```typescript theme={null}
  // ❌ ERROR
  const text = await resumed.text();
  const meta = await resumed.finalResponse(); // StreamError!

  // ✅ CORRECTO
  const response = await resumed.finalResponse();
  const text = response.message;
  const meta = response.metadata;
  ```
</Warning>

## Que pasa al rechazar

El comportamiento al rechazar depende de la configuracion de la instruccion:

| `onReject` | Comportamiento                                                      |
| ---------- | ------------------------------------------------------------------- |
| `stop`     | El agente se detiene y muestra un mensaje de rechazo                |
| `continue` | El agente continua sin ejecutar la tool                             |
| `fallback` | El agente ejecuta una accion alternativa definida en la instruccion |

<Info>
  Las acciones HITL se configuran desde la plataforma en la seccion **Instrucciones**. Cada instruccion puede tener una accion asociada a una tool especifica. Ver [Instrucciones](/platform/instructions) para mas detalles.
</Info>

## respondToAction

Firma completa del metodo:

```typescript theme={null}
thaliq.agent.respondToAction(options: RespondToActionOptions): AgentStream
```

| Parametro         | Tipo              | Requerido | Descripcion                                      |
| ----------------- | ----------------- | :-------: | ------------------------------------------------ |
| `conversationId`  | `string`          |     Si    | ID de la conversacion                            |
| `originalMessage` | `string`          |     Si    | Mensaje original que disparo la accion           |
| `actionResponse`  | `ActionResponse`  |     Si    | Respuesta construida con `ActionResponseBuilder` |
| `onEvent`         | `(event) => void` |     No    | Callback por evento                              |
| `signal`          | `AbortSignal`     |     No    | Signal para cancelar                             |

Retorna un `AgentStream` que puedes consumir con `for await`, `text()`, o `finalResponse()`.

## PendingAction

El objeto `pendingAction` contiene toda la informacion necesaria para renderizar la UI:

```typescript theme={null}
interface PendingAction {
  type: 'consent' | 'confirm' | 'select' | 'form';
  instructionId: string;     // ID de la instruccion (necesario para responder)
  message: string;           // Mensaje a mostrar al usuario
  options?: ActionOption[];  // Opciones (solo para 'select')
  fields?: ActionField[];    // Campos (solo para 'form')
  onReject?: 'stop' | 'continue' | 'fallback';
}

interface ActionOption {
  label: string;
  value: string;
}

interface ActionField {
  name: string;
  label: string;
  type: 'text' | 'number' | 'email' | 'textarea' | 'select';
  required?: boolean;
  placeholder?: string;
  options?: ActionOption[];  // Solo para fields de tipo 'select'
}
```
