aethexDocs

SDK React

Déployez un agent vocal en production dans votre application React en une après-midi, sur le web ou en React Native. useAethexCall() masque toute la plomberie WebRTC, la signalisation, ICE et l'audio derrière un seul hook headless : vous construisez l'expérience, nous gérons le transport temps réel. Compatible SSR et StrictMode, prêt pour le App Router de Next.js, et le même hook s'exécute nativement sur iOS et Android via Expo.

Installation

Ajoutez le paquet. react et react-dom sont des dépendances de pair pour le hook ; l'entrée ./core, indépendante du framework, n'en a besoin d'aucune.

npm
bash
npm install @aethexai/react react react-dom

Votre premier appel

Voici l'intégration complète. Aucun socket à câbler, aucune connexion peer à surveiller, aucun élément <audio> à gérer. Appelez start() sur un geste utilisateur et l'agent est en ligne. start() ne lève jamais d'exception : vous pilotez votre interface directement depuis status et error.

CallButton.tsx
tsx
"use client"
import { useAethexCall, useAudioLevel } from "@aethexai/react"

export function CallButton() {
  const { status, isConnected, start, stop, remoteStream, error } = useAethexCall({
    agentId: "8a1b2c3d-4e5f-6789-abcd-ef0123456789",
    apiBaseUrl: "https://your-proxy.example.com", // your proxy, never a key
    onConnected: () => console.log("live"),
    onError: (e) => console.error(e.code, e.recoverable),
  })
  const { level } = useAudioLevel(remoteStream)

  if (error) return <p>Call failed: {error.code}</p>
  return (
    <button onClick={isConnected ? stop : start}>
      {status === "connecting" ? "Connecting..." : isConnected ? `Hang up (${level.toFixed(2)})` : "Call"}
    </button>
  )
}

useAethexCall

Un seul hook gère tout l'appel. Donnez-lui un agent et l'URL de votre proxy, récupérez un état réactif et quelques contrôles.

Options

agentId est toujours requis. Fournissez exactement l'un des deux : apiBaseUrl (votre proxy côté serveur) ou getToken (jeton éphémère par appel). Tout le reste ajuste les callbacks de cycle de vie, la résilience et le transport.

UseAethexCallOptions
ts
interface UseAethexCallOptions {
  agentId: string          // the agent to call
  apiBaseUrl?: string      // your proxy (holds the key server-side)...
  getToken?: () => string | Promise<string>  // ...or mint a token per call, no proxy

  // Lifecycle callbacks
  onConnected?: () => void
  onEnded?: () => void
  onError?: (error: AethexError) => void
  onMetrics?: (metrics: PipelineMetrics) => void

  // Resilience & transport
  iceRestart?: boolean     // auto-heal dropped calls (default true)
  maxIceRestarts?: number  // consecutive attempts (default 1)
  timeoutMs?: number       // signaling timeout in ms (default 15000)
  headers?: Record<string, string>  // e.g. a short-lived proxy token
  audioConstraints?: MediaTrackConstraints
}

Valeurs de retour

Un état réactif que vous affichez directement dans votre interface, plus start, stop et interrupt. status passe par cinq valeurs : idle, connecting, connected, ended et error.

UseAethexCallResult
ts
interface UseAethexCallResult {
  status: "idle" | "connecting" | "connected" | "ended" | "error"
  isConnecting: boolean
  isConnected: boolean
  remoteStream: MediaStream | null   // pass to useAudioLevel()
  metrics: PipelineMetrics | null
  error: AethexError | null          // .code and .recoverable
  sessionId: string | null

  start: () => Promise<void>         // never throws
  stop: () => void                   // idempotent
  interrupt: () => void              // barge in on the agent
  getRemoteStatus: () => Promise<SessionStatusResponse>
}

Visualiseur audio en direct

Passez le remoteStream de l'agent à useAudioLevel pour obtenir une amplitude lissée de 0 à 1 à chaque frame d'animation. Animez une barre, un anneau ou un orbe pulsant, selon votre marque.

useAudioLevel
tsx
import { useAudioLevel } from "@aethexai/react"

const { level } = useAudioLevel(remoteStream) // 0..1, smoothed per animation frame
// drive a bar, a ring, a pulsing orb, whatever fits your brand

Composants prêts à l'emploi

Vous préférez ne pas construire d'interface du tout ? Importez un composant prêt à l'emploi et entièrement accessible, et passez en ligne en une ligne. Boutons natifs avec support clavier complet, une seule région de statut aria-live, et un visualiseur qui se dégrade proprement sous prefers-reduced-motion. L'orbe flottant AethexVoiceOrb vous donne la bulle vocale classique en bas à droite, sans aucun travail de mise en page.

widgets
tsx
import { AethexCallButton, AethexVoiceWidget, AethexVoiceOrb } from "@aethexai/react/widgets"

// A one-line call button
<AethexCallButton agentId={AGENT_ID} apiBaseUrl={PROXY_URL} />

// A full widget with a live visualizer and a title
<AethexVoiceWidget agentId={AGENT_ID} apiBaseUrl={PROXY_URL} title="Talk to support" />

// A floating voice orb (bottom-right by default)
<AethexVoiceOrb agentId={AGENT_ID} apiBaseUrl={PROXY_URL} />

Transcriptions

Récupérez la transcription complète tour par tour une fois l'appel terminé, indexée par son sessionId.

getTranscript
ts
import { getTranscript } from "@aethexai/react"

const turns = await getTranscript({ apiBaseUrl, sessionId })

Des appels qui résistent au monde réel

Les vraies conversations se déroulent sur de vrais réseaux. Quand la connexion d'un appel établi tombe, par exemple lors du passage du wifi à la 4G, le SDK renégocie et rétablit l'appel automatiquement au lieu de raccrocher. Activé par défaut ; montez le curseur avec maxIceRestarts, ou passez iceRestart: false pour échouer rapidement.

résilience
ts
useAethexCall({ agentId, apiBaseUrl })                    // auto-heal on (default)
useAethexCall({ agentId, apiBaseUrl, maxIceRestarts: 2 }) // recover harder
useAethexCall({ agentId, apiBaseUrl, iceRestart: false }) // fail fast instead

Statut de session côté serveur

getRemoteStatus() renvoie la vision de la session par la plateforme elle-même : sa durée, son nombre de tours et son cycle de vie, distincte du status local. Fonctionne pendant l'appel et après sa fin.

getRemoteStatus
ts
const { getRemoteStatus } = useAethexCall({ agentId, apiBaseUrl })

// the server's own view, works during and after the call
const s = await getRemoteStatus()
// { session_id, status, duration_s, turn_count, ... }

Des erreurs typées, sans deviner

Chaque échec est une AethexError avec un code stable et exploitable dans un switch : plus besoin de comparer des messages. Vérifiez error.recoverable pour décider de réessayer, et affinez avec isAethexError().

AethexErrorCode
ts
import { isAethexError } from "@aethexai/react"

// Every failure is a typed AethexError with a stable .code:
type AethexErrorCode =
  | "unsupported_browser"  // no WebRTC / getUserMedia
  | "mic_denied"           // user or policy blocked the microphone
  | "mic_missing"          // no audio input device
  | "connect_failed"       // could not open the session
  | "offer_failed"         // could not negotiate media
  | "quota_exceeded"       // 429, honors Retry-After
  | "payment_required"     // 402, billing
  | "capacity"             // 503, no voice capacity right now
  | "peer_failed"          // the connection dropped and could not recover
  | "timeout"              // a signaling request timed out
  | "aborted"              // the call was stopped mid-flight
  | "network"              // offline, CORS, or DNS
  | "unknown"

if (isAethexError(error) && error.recoverable) retry()

Sécurité

Votre clé ae_live_... vit sur un proxy que vous hébergez et n'atteint jamais le navigateur ; apiBaseUrl pointe vers ce proxy. WebRTC et l'accès au microphone exigent HTTPS (ou localhost).

Attention
Le SDK lève une exception si apiBaseUrl ressemble à une clé API. N'envoyez jamais de clé au client.

Jetons éphémères (sans proxy)

Plutôt que d'héberger un proxy, générez un jeton de courte durée sur votre serveur et transmettez-le au client avec getToken. Passez getToken et retirez apiBaseUrl : le SDK communique alors directement avec l'API Aethex, votre seul code côté serveur est une route d'une ligne qui appelle POST /conversation/token avec votre clé API. Le jeton est limité à un seul agent et expire après un court délai (au plus la durée maximale d'appel configurée pour l'agent), il est donc sûr côté navigateur. Générez un nouveau jeton pour chaque appel afin de limiter la fenêtre d'exposition. Voir l'API Conversation pour le point de terminaison de génération.

getToken
tsx
useAethexCall({
  agentId,
  // Drop apiBaseUrl; the SDK talks to the Aethex API directly with this token.
  getToken: async () => {
    const res = await fetch("/api/aethex-token", { method: "POST" })
    if (!res.ok) throw new Error("Token mint failed")
    return (await res.json()).token
  },
})

React Native (Expo)

Le même hook useAethexCall fonctionne en React Native : @aethexai/react fournit une build native que Metro résout automatiquement, l'import est donc identique. En natif, il s'appuie sur react-native-webrtc et route l'audio de l'agent vers le haut-parleur. React Native n'a pas de CORS, connectez-vous donc directement avec le flux de jeton éphémère (getToken), sans aucun proxy. WebRTC est un module natif : cela nécessite une build de développement Expo (cela ne fonctionne pas dans Expo Go). Le widget AethexVoiceOrb s'affiche nativement via Skia, vous pouvez donc l'intégrer directement ; les widgets bouton et panneau sont réservés au web.

Configuration Expo
bash
# WebRTC is a native module, so this needs an Expo dev build (not Expo Go).
npx expo install react-native-webrtc @config-plugins/react-native-webrtc expo-dev-client

# app.json -> expo.plugins:
#   ["@config-plugins/react-native-webrtc",
#     { "microphonePermission": "Allow $(PRODUCT_NAME) to use your microphone." }]

# For the AethexVoiceOrb widget: Skia, its reanimated peer, and loudspeaker routing.
npx expo install @shopify/react-native-skia react-native-reanimated react-native-incall-manager
# babel.config.js: add "react-native-reanimated/plugin" (last in the plugins list)

npx expo prebuild --clean
npx expo run:ios --device   # iOS needs a real device (the Simulator has no mic)
npx expo run:android        # an emulator is fine with host-mic input

L'orbe vocale, en natif

AethexVoiceOrb est le moyen le plus rapide de lancer un appel natif : le même import que sur le web affiche une orbe Skia sur iOS et Android, avec le routage vers le haut-parleur, la coupure du micro, le raccrochage et une évaluation post-appel facultative gérés pour vous. Définissez sa couleur avec agentName et choisissez une texture avec orbType. En natif, les hooks de niveau audio renvoient 0 (pas de Web Audio), l'orbe s'anime donc d'elle-même pendant l'appel au lieu de réagir à l'audio.

AethexVoiceOrb (natif)
tsx
import { AethexVoiceOrb } from "@aethexai/react/widgets"

// Same import as web; Metro resolves the Skia-rendered native orb.
<AethexVoiceOrb
  agentId={AGENT_ID}
  agentName="Kora"       // seeds the orb's colour + texture
  getToken={getToken}    // mint on your server; no key on the device
  orbType="aurora"
  controls               // mute + hang up
  feedback               // post-call rating
/>

Cœur indépendant du framework

Pas sur React ? Le même moteur est fourni sous forme de classe VoiceCall en TypeScript pur, dans @aethexai/react/core : des callbacks au lieu de hooks, tout le reste identique.

VoiceCall
ts
import { VoiceCall } from "@aethexai/react/core"

const call = new VoiceCall({
  agentId: AGENT_ID,
  apiBaseUrl: PROXY_URL, // never the direct API or a key
  callbacks: {
    onStatusChange: (s) => console.log(s),
    onRemoteStream: (stream) => { /* auto-played via a managed <audio> */ },
    onError: (err) => console.error(err.code, err.recoverable),
    onClose: () => console.log("call ended"),
  },
})

await call.start()
call.stop() // idempotent teardown