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 install @aethexai/react react react-domVotre 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.
"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.
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.
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.
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 brandComposants 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.
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.
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.
useAethexCall({ agentId, apiBaseUrl }) // auto-heal on (default)
useAethexCall({ agentId, apiBaseUrl, maxIceRestarts: 2 }) // recover harder
useAethexCall({ agentId, apiBaseUrl, iceRestart: false }) // fail fast insteadStatut 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.
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().
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).
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.
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.
# 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 inputL'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.
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.
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