# SDK Node

> Installez le SDK Node de Thalovant pour vos services, scripts, bots et agents écrits en JavaScript ou en TypeScript.

Source: https://docs.thalovant.com/fr/developers/sdks/node/

---
{/* Last Edit: Claude Opus 5.5 - 2026-09-28 - Motive: Canadian French translation of developers/sdks/node.mdx; terms per docs/french-glossary.md (AMF, serveur), and review fixes. */}

Utilisez le SDK Node lorsque votre client ou votre agent s'exécute en JavaScript ou en TypeScript.

Le nom du paquet est `@thalovant/sdk`. Node.js 20 ou une version plus récente est requis.

## Installation

Installez le paquet avec `npm install @thalovant/sdk` :

Épinglez la version lorsque vous avez besoin d'une compilation reproductible. La version actuelle figure dans
[Versions prises en charge](/fr/developers/sdks/#versions-prises-en-charge).

## Première requête

```ts

const api = new ThalovantControlPlane();

const publicHubs = await api.listPublicHubs({ limit: 1 });
const [hub] = publicHubs.data as Array<{ id: string; slug: string; title: string }>;
console.log(hub.title, hub.slug);

await api.login("you@example.com", "password");

const result = await api.createClientIdentity(hub.id, {
  name: "node-demo-client",
  preferredProtocols: ["wss", "https", "mqtt"],
});

const client = new ThalovantClient(result.identity, { protocol: "wss" });
try {
  const reply = await client.ask("Tell me a short clean joke.");
  console.log(reply.text);
} finally {
  await client.close();
}
```

`new ThalovantControlPlane()` utilise `https://api.thalovant.com` par défaut.

  Gardez `result.identity` secret. Les actions d'API authentifiées exigent que l'accès à l'API soit activé dans l'espace de travail.

## Se connecter avec l'authentification multifacteur

Les comptes où l'authentification multifacteur (AMF) est activée doivent fournir un code TOTP ou un code de récupération à usage unique lors de la connexion. Sans l'un ou l'autre, l'API refuse la connexion avec une erreur HTTP 401 et le code `mfa_required`. Chaque option n'est envoyée que si elle est fournie. La prise en charge de l'AMF exige le SDK 0.2.23 ou une version plus récente.

```ts
await api.login("you@example.com", "password", { otpCode: "123456" });

// Or use a one-time recovery code instead:
await api.login("you@example.com", "password", { recoveryCode: "abcd-efgh-ijkl" });
```

## Se connecter sans mot de passe

La connexion par appareil demande à votre navigateur d'approuver la connexion : vos scripts, bots et agents ne manipulent donc jamais votre mot de passe. Le SDK affiche un code court et l'adresse `https://dash.thalovant.com/activate`, ouvre cette page lorsqu'il le peut, puis attend que vous approuviez la demande dans le tableau de bord avec votre connexion habituelle, y compris la connexion Google ou l'AMF. Une fois la demande approuvée, le SDK détient un jeton d'API révocable, limité à certaines portées.

La connexion par appareil exige le SDK 0.2.25 ou une version plus récente. Pour approuver la demande, l'espace de travail doit avoir un forfait payant; un forfait gratuit reçoit une erreur HTTP 402.

```ts
// Prints: To sign in, visit https://dash.thalovant.com/activate and enter the code XXXX-XXXX
const token = await api.loginWithBrowser({ clientName: "my-laptop" });

// api.accessToken is now set, exactly like after api.login(...).
const page = await api.listHubs({ limit: 50 });
```

Gérez le jeton obtenu sur la [page API Tokens](/fr/account/api-tokens/) du tableau de bord.

## S'authentifier avec un jeton d'API

Passez un jeton d'API enregistré lorsque le processus doit démarrer déjà authentifié, par exemple pour les tâches d'intégration continue et les services. Créez-en un sur la [page API Tokens](/fr/account/api-tokens/) ou avec `loginWithBrowser`. Cette page affiche les portées, l'expiration et la dernière utilisation, et permet de révoquer le jeton à tout moment.

```ts
const api = new ThalovantControlPlane("https://api.thalovant.com", {
  accessToken: process.env.THALOVANT_API_TOKEN,
});

// Ready immediately; no login call needed.
const page = await api.listHubs({ limit: 50 });
```

## Utiliser le SDK dans un navigateur

Le SDK fonctionne aussi dans les navigateurs, derrière un outil de regroupement (bundler). Il faut pour cela le SDK 0.2.24 ou une version plus récente.

Le plan de contrôle, y compris `login`, `listPublicHubs`, `createClientIdentity`, la mémoire et les analyses, utilise le `fetch` global. `ThalovantClient` fonctionne avec les protocoles `wss` et `https` au moyen du `WebSocket` global.

Le paquet fournit une table `browser` à côté de son entrée `exports`. Les outils de regroupement comme esbuild, webpack, Vite et Rollup choisissent donc automatiquement les modules compatibles avec les navigateurs et n'intègrent jamais `ws`, `mqtt` ni les modules intégrés `node:` dans les paquets web. Regroupez-le comme n'importe quelle autre dépendance :

```bash
esbuild app.js --bundle --platform=browser --outfile=dist/app.js
```

```ts
// app.js — runs in the browser after bundling

const api = new ThalovantControlPlane();
await api.login(email, password);
const result = await api.createClientIdentity(hubId, { name: "web-kiosk" });

const client = new ThalovantClient(result.identity, { protocol: "wss" });
try {
  const reply = await client.ask("Hello from the browser.");
  console.log(reply.text);
} finally {
  await client.close();
}
```

Mises en garde pour les navigateurs :

- Le protocole `mqtt` reste réservé à Node. Construire le transport MQTT dans un navigateur lève `ThalovantUnsupportedProtocolError` avec un message clair; utilisez plutôt `wss` ou `https`.
- Les fichiers d'identité et les configurations YAML restent réservés à Node. `ThalovantIdentity.fromFile()`, `fromConfig()` et `defaultConfigPath()` lèvent une erreur dans les navigateurs; construisez `ThalovantIdentity` à partir d'un objet en mémoire, comme le résultat de `createClientIdentity`.
- Dans les navigateurs, le SDK conserve ses clés dans `localStorage`, sous l'espace de noms `thalovant:noise` par défaut. Tout ce qui y est stocké peut être lu par les scripts de cette origine. Conservez cet état d'un chargement de page à l'autre, et utilisez un espace de noms et une identité distincts pour chaque onglet actif de façon indépendante. La file d'attente du navigateur ne coordonne qu'une seule page; elle ne sérialise pas les modifications entre les onglets. Si le stockage du navigateur n'est pas disponible ou qu'une écriture échoue, le nouvel état ne dure que le temps de cette page. Un chargement ultérieur peut alors être refusé par un hub qui a épinglé la clé précédente du client. Restaurez l'état d'origine ou provisionnez une identité de client de remplacement au lieu de réinitialiser automatiquement les épinglages.
- Une identité de client reste secrète, même pour un hub public. Obtenez-la au moment de l'exécution pour l'utilisateur ou l'appareil autorisé; n'intégrez jamais une identité partagée, un jeton d'API ou un mot de passe dans le paquet JavaScript livré. Demandez une [origine SDK](/fr/developers/sdk-origins/) approuvée avant d'appeler l'API à partir d'un domaine de navigateur appartenant à un client.

## Utiliser une identité enregistrée

```ts

const client = await ThalovantClient.fromConfig({ profile: "prod", protocol: "wss" });
try {
  const reply = await client.ask("What can this hub do?");
  console.log(reply.text);
} finally {
  await client.close();
}
```

Les fichiers d'identité bruts fonctionnent aussi :

```ts

const client = await ThalovantClient.fromIdentityFile("_identity.json");
try {
  const reply = await client.ask("What can this hub do?");
  console.log(reply.text);
} finally {
  await client.close();
}
```

Les variables d'environnement fonctionnent aussi :

```ts

const client = ThalovantClient.fromEnv();
```

## Où le SDK conserve ses clés

Dans Node.js, le SDK se connecte par WSS, par HTTPS ou par MQTT sur TLS; les versions pour navigateur utilisent WSS et HTTPS. Chaque connexion est chiffrée et authentifiée avant l'envoi de quoi que ce soit.

L'option facultative `noiseStateDir` est acceptée par le constructeur du client ainsi que par `fromIdentityFile`, `fromConfig` et `fromEnv`. Dans Node.js, elle désigne le répertoire privé où le SDK conserve ses clés et les identités de hub qu'il a enregistrées; conservez-le d'un redémarrage à l'autre. Dans les navigateurs, la même option nomme un espace de noms `localStorage` plutôt qu'un chemin. Utilisez une identité distincte pour chaque processus client qui s'exécute en même temps.

MQTT exige un point de terminaison TLS. Régler `tls: true` fait passer `mqtt://` à `mqtts://` et `ws://` à `wss://`; les schémas effectifs non pris en charge ou en clair sont refusés avant l'envoi des identifiants du courtier (broker). La connexion MQTT, l'abonnement, l'authentification et la présence en ligne partagent le délai de connexion. Le nettoyage après un échec HTTPS reste lui aussi dans les limites du délai de connexion d'origine.

```typescript
const client = await ThalovantClient.fromIdentityFile("identity.json", {
  protocol: "https",
  noiseStateDir: "/path/to/private/persistent/sdk-state",
});
```

HTTPS conserve sa session d'une reconnexion à l'autre et nettoie sa connexion précédente avant d'en ouvrir une nouvelle. Après la perte de la connexion au courtier, MQTT s'authentifie de nouveau. `healthcheck().handshakeComplete` reste à false tant que l'authentification n'est pas terminée; un socket ouvert ou une réponse HTTP réussie ne suffit pas à lui seul.

`connect(timeoutMs?, signal?)` utilise une seule échéance pour le nettoyage en file d'attente, l'établissement de la connexion et la disponibilité authentifiée, avec une valeur par défaut de 6 000 ms. `close(timeoutMs?)` limite l'attente de l'appelant avec la même valeur par défaut. Si la fermeture dépasse le délai, le nettoyage conserve la maîtrise de la connexion. `await client.waitForClosed()` observe le nettoyage réel le plus récent, sans délai d'expiration, et en conserve l'échec éventuel. Attendez la fin de ce nettoyage avant de réutiliser l'identité.

Utilisez de nouveaux identifiants de demande (request IDs) pour Ask et de nouveaux identifiants d'interrogation (query IDs) pour Query; des identifiants générés sont utilisés par défaut. Un collecteur concurrent qui porte le même identifiant est refusé sur le même client. Les espaces de noms sont indépendants. Ne réutilisez pas un identifiant pour une opération logique ultérieure, même après une annulation : une réponse retardée peut encore le porter. Réutilisez un identifiant de session pour conserver le contexte de la conversation.

Node écrit ses clés enregistrées de façon atomique entre les processus. Des clés enregistrées endommagées arrêtent l'opération sans réinitialiser ce qui a été enregistré. Si un processus qui a planté laisse `.noise-state.lock` derrière lui, confirmez que le processus est bien arrêté avant de supprimer uniquement le fichier de verrou, et gardez les fichiers de clés. Ce verrou ne coordonne ni les sessions de hub actives ni les onglets de navigateur.

## Choisir un protocole

```ts
const identity = result.identity;

console.log(identity.enabledProtocols());
console.log(identity.endpointFor("wss"));
console.log(identity.endpointFor("https"));
console.log(identity.endpointFor("mqtt"));

for (const protocol of ["wss", "https", "mqtt"] as const) {
  if (!identity.supportsProtocol(protocol)) continue;
  if (protocol === "mqtt" && !identity.mqtt) continue;

  const client = new ThalovantClient(identity, { protocol });
  try {
    const reply = await client.ask(`Reply over ${protocol}.`);
    console.log(protocol, reply.text);
  } finally {
    await client.close();
  }
}
```

MQTT exige les identifiants de courtier `identity.mqtt` renvoyés pour ce client.

Pour les détails sur le courtier, consultez [MQTT](/fr/developers/mqtt/).

## Ajouter du contexte

```ts

const context = buildClientContext({}, {
  userId: "user-42",
  userName: "Ada",
  source: "checkout-kiosk",
  platform: "kiosk",
  locale: "en-US",
  channel: "chat",
});

const reply = await client.ask("Show the next instruction.", { context });
console.log(reply.text);
```

## Écouter les événements

```ts

const sub = client.on(EVENT_SPEAK, event => {
  console.log(event.text);
});

try {
  await client.ask("Say the current status.");
} finally {
  sub.close();
}
```

Enregistrez l'écouteur avant l'envoi pour qu'il puisse observer les réponses rapides. `ask()` corrèle aussi sa propre réponse; l'abonnement observe les événements du bus correspondants pendant l'exécution de la requête.

## Lister ce qu'on peut demander au hub

Un client connecté peut lister chaque intention à laquelle son hub répond, par langue, avec les phrases qu'une personne prononce pour atteindre chacune d'elles. Il le fait dans sa propre session, sans jeton du plan de contrôle. Consultez [Que peut-on demander à mon hub?](/fr/concepts/hub-intents/) pour savoir ce que le hub renvoie, quels types de messages la connexion doit être autorisée à publier et à quoi ressemblent un refus ou une liste en échec.

Depuis le SDK 0.2.38, les appels sont `client.intents(languages, options)`, qui renvoie l'inventaire regroupé par compétence, `client.listIntents(lang, options)`, qui renvoie les lignes d'enregistrement, et `client.describeIntent(skillId, intentName, lang, options)`, qui renvoie les définitions derrière une intention.

```ts

const client = await ThalovantClient.fromIdentityFile("_identity.json");
try {
  const inventory = await client.intents(["en-us", "fr-fr"]);
  console.log(inventory.source, inventory.languages);
  for (const skill of inventory.skills) {
    for (const intent of skill.intents) {
      console.log(intent.id, intent.engine, intent.examples("fr-fr"));
    }
  }
} catch (error) {
  if (error instanceof ThalovantPolicyDeniedError) {
    console.error("refused:", error.deniedType, "allowed:", error.allowed);
  }
  throw error;
} finally {
  await client.close();
}
```

`intents()` interroge `ovos.intent.list` une fois par langue, puis complète les phrases que la liste ne contenait pas. `{ describe: false }` s'arrête aux noms, aux moteurs et à l'état d'activation. `examples(lang, limit)` préfère les phrases complètes à celles qui contiennent un `{slot}`, puis les formulations les plus complètes, jusqu'à huit mots; `phrasesFor(lang)` les renvoie toutes, et `inventory.asObject()` est prêt pour `JSON.stringify`. Les étiquettes de langue se comparent sans tenir compte de la casse ni de la différence entre `_` et `-` : `fr-fr` et `fr_FR` constituent donc une seule et même demande.

Un hub qui refuse une requête la rejette immédiatement avec `ThalovantPolicyDeniedError`, une `ThalovantRuntimeError` qui porte `deniedType`, `code`, `reason` et `allowed`. Avec la valeur par défaut `{ fallback: true }`, un `ovos.intent.list` refusé ou resté sans réponse renvoie plutôt les noms seulement : `inventory.source` vaut `"engine-manifests"`, `inventory.denied` vaut `["ovos.intent.list"]` et `inventory.hasPhrases` vaut false. Un hub qui accepte la demande de liste et y répond avec `ok: false` provoque un rejet avec `ThalovantRuntimeError`, qui porte le texte d'erreur propre au hub, car une liste en échec n'est pas un hub vide. Depuis la version 0.2.39, ce rejet remplace la liste vide que le SDK renvoyait auparavant.

## Fonctions utiles

  
    Envoyez une requête et recevez du texte, de la parole et des éléments d'affichage normalisés.
  
  
    Attendez un événement du hub correspondant, avec un délai d'expiration.
  
  
    Envoyez une action de bouton, de menu ou d'outil.
  
  
    Envoyez un code-barres, une valeur QR, un numéro de série ou une valeur exacte saisie.
  

Pour la liste complète des méthodes, consultez [Fonctions des SDK](/fr/developers/sdk-functions/).
Utilisez `api.getOperation(operationId)` pour suivre une commande acceptée; consultez [Opérations](/fr/developers/operations/).

## Provisionner des hubs

Depuis le SDK 0.2.28, le plan de contrôle peut créer des hubs, des groupes de runtime et des installations de compétences. Parcourir le catalogue avec `listMarketplaceSkills()` exige `hubs:read` et fonctionne avec tous les forfaits. `createHub`, `createRuntimeGroup`, `installRuntimeGroupSkill`, `releaseHub` et `releaseRuntimeGroup` exigent `hubs:write` et un forfait payant.

```ts
const group = await api.createRuntimeGroup({ name: "kiosks" });
let hub = await api.createHub({ name: "joke-garden", runtimeGroupId: group.id as string, spec: {} });

hub = await api.getHub(hub.id as string);
await api.updateHub(hub.id as string, { active: false }, { etag: hub.etag as string });
```

`updateHub` et `deleteHub` exigent l'etag actuel du hub. Consultez [Provisionner des hubs](/fr/developers/provisioning/) pour le déroulement complet, les champs immuables et le tableau des erreurs.

À partir du SDK `0.3.15`, `listHubSkills`, `installHubSkill`, `updateHubSkill` et `removeHubSkill` gèrent les compétences d'un hub désigné par son identifiant. `listHubSkills` se résout avec `{ data, source, observed_at, ... }` (`for (const skill of (await api.listHubSkills("hub-id")).data)`), les écritures se résolvent avec une `HubSkillOperation`, et `{ wait: true }` interroge cette opération avec un budget d'interrogation par défaut de 120 secondes (`timeoutMs`). Consultez [Ajouter une compétence à un hub](/fr/manage/add-a-skill-to-a-hub/).

Aucune nouvelle lecture d'état ne démarre à l'échéance d'interrogation ou après; cette échéance n'annule pas une requête HTTP déjà en cours. Si une lecture d'état échoue, l'erreur conserve l'identifiant de l'opération acceptée : vous pouvez ainsi reprendre avec `getOperation` sans répéter l'écriture. Une erreur d'API sous-jacente, assainie, est conservée comme cause; les exceptions inattendues sont omises de la chaîne affichée.

## Problèmes courants

| Symptôme | Vérification |
| --- | --- |
| Jeton d'accès manquant | Appelez `api.login(...)` ou `api.loginWithBrowser(...)` avant les actions d'API privées, ou passez `accessToken` à `ThalovantControlPlane`. |
| HTTP 401 avec le code `mfa_required` | Passez `otpCode` ou `recoveryCode` dans les options de connexion. |
| L'accès à l'API exige un forfait payant | Passez l'espace de travail à un forfait supérieur avant de provisionner des ressources privées au moyen de l'API. |
| Protocole non pris en charge | Activez ce protocole sur le hub et créez une nouvelle identité. |
| MQTT échoue immédiatement | Confirmez que l'identité possède des identifiants de courtier `mqtt` et que le client ne s'exécute pas dans un paquet pour navigateur. |
## Découverte des gestionnaires de repli

L'inventaire comprend `fallbacks` et `fallbacksKnown`. Utilisez `inventory.mayAnswer("en-us")` pour une vérification prudente de la langue : un hub dont la prise en charge du repli est inconnue peut quand même répondre. La sonde prend au plus 1,5 seconde, connexion, envoi et attente compris. Un résultat vide confirmé se distingue d'un refus, d'un échec, d'une absence de réponse ou d'une enveloppe de réponse mal formée. Les tableaux acceptés conservent les gestionnaires utilisables et ignorent les lignes invalides. Consultez [la découverte des gestionnaires de repli](/fr/concepts/hub-intents/#gestionnaires-de-repli-et-disponibilité-des-langues).

Les redirections du plan de contrôle sont refusées. Les appels d'API authentifiés et les corps de requête exigent HTTPS, sauf pour les points de terminaison de développement explicitement en boucle locale (loopback). Consultez [la sécurité des identifiants d'API](/fr/developers/sdks/#sécurité-des-identifiants-dapi).

Passez un `AbortSignal` comme `signal` dans les options de `ask`, `query` et `waitForEvent`, ou comme deuxième argument de `connect`. L'annulation met fin à l'attente de l'appelant; tout travail de transport déjà accepté en garde la maîtrise jusqu'à ce qu'il se termine ou échoue sans danger. Les requêtes ne sont pas rejouées automatiquement. Ask utilise des fenêtres fixes pour la première parole et pour les réponses vides, à l'intérieur de son échéance d'origine; Query se termine sur son événement final.

## Gérer les compétences par l'intermédiaire d'un hub

Le SDK peut lister, installer, mettre à jour et retirer les compétences du groupe de runtime
d'un hub, et en consulter l'historique. **Tous les hubs qui partagent ce groupe de runtime sont touchés.** L'UUID
d'un hub sélectionne le groupe; il ne crée pas d'installation isolée pour ce hub.
Un jeton restreint doit couvrir tous les hubs que sert le groupe.

Les lectures exigent `hubs:inspect` (`hubs:read` l'inclut). Les écritures exigent `hubs:write`,
un forfait payant admissible et la propriété. Utilisez `latest` ou une version publiée précise
pour l'installation et les mises à jour. Une écriture acceptée renvoie un `operation_id`;
l'acceptation ne signifie pas que le runtime a fini d'installer la compétence.

| Action | Méthode |
|---|---|
| Lister | `listHubSkills` |
| Historique | `listHubSkillHistory` |
| Installer | `installHubSkill` |
| Mettre à jour | `updateHubSkill` |
| Retirer | `removeHubSkill` |

L'historique renvoie l'enveloppe JSON de l'API avec des entrées `event` et `operation`,
de la plus récente à la plus ancienne, en conservant les champs d'acteur et de version qui peuvent être nuls. La limite est de 1 à 200.

Utilisez l'option d'attente existante pour suivre l'opération acceptée. Un échec d'interrogation
conserve l'identifiant de son opération; consultez cette opération au lieu de répéter l'écriture.

## Fonctions de requête et mises à jour sûres de la configuration

*Disponible depuis la version `0.5.1`.*

Les indications de requête transmettent une langue reconnue, un pipeline d'intentions ordonné et
l'emplacement de l'appelant, sans modifier le contexte de l'appelant. Les indications vides sont omises. La
fonction d'emplacement exige une ville et omet les coordonnées invalides ou égales à zéro/zéro.
Le hub valide les indications de langue par rapport aux langues configurées.

Les réponses exposent leur langue déclarée, leurs événements de parole et d'audio ordonnés et le
nombre de médias abandonnés. Les extraits intégrés par les compétences sont limités à 4 Mio chacun et à
16 Mio par réponse, vérifiés avant la conservation et le décodage. L'audio ne prolonge pas
la fenêtre de stabilisation de la réponse. Le décodage accepte des octets hexadécimaux séparés par des
espaces ASCII; il ne récupère jamais une URL ni un chemin de fichier fournis par une compétence.
La lecture relève de l'application. Toute fonction `play` ou `Play` ci-dessous appartient à votre application.

```ts
const location = buildLocation({ city: "Montréal", country: "CA", latitude: 45.5, longitude: -73.5 });
const reply = await client.ask("Quel temps fait-il ?", { sttLang: "fr-ca", location });
for (const event of reply.mediaEvents ?? []) if (event.isAudio) play(event.audioBytes());
const sentences = intent.examples("en-us", 2, { speakable: true, slots: { location: "Montréal" } });
await api.updateRuntimeGroupConfig(groupId, { tts: { module: "piper" } });
// Explicit full replacement:
await api.updateRuntimeGroupConfig(groupId, fullConfig, { merge: false });
```

La fusion protégée exige les portées `hubs:read` et `hubs:write` et un forfait payant.
La fusion sûre exige une API dont le GET de configuration renvoie une `revision` valide
et dont le PUT de configuration vérifie `expected_revision`. Le SDK relit la configuration et
réapplique le delta d'origine seulement après une erreur HTTP 412, avec au plus trois tentatives.
Les tableaux et les valeurs scalaires sont remplacés; les objets sont fusionnés récursivement. Les personas ne sont remplacés
que s'ils sont fournis explicitement. Les échecs de connexion, les redirections, les autres états
et les résultats d'écriture ambigus ne font jamais l'objet d'une nouvelle tentative. Aucun repli PATCH non sûr n'est utilisé.
Les remplacements inconditionnels doivent tout de même être coordonnés avec les autres processus qui écrivent.

Utilisez l'opération de remplacement explicite montrée ci-dessus lorsque vous voulez un remplacement complet,
y compris avec une API plus ancienne. Le code existant qui compte sur le
remplacement doit l'activer explicitement lors de la mise à jour. Les motifs d'intention bruts restent la
valeur par défaut; les exemples prononçables retirent les parties facultatives, choisissent parmi les variantes et
substituent les valeurs d'emplacement fournies par l'appelant, tout en gardant la priorité aux phrases complètes.

Les limites audio s'appliquent à des bornes supérieures calculées sur la longueur encodée avant le décodage : les
espaces de mise en forme consomment donc aussi le budget. Comme avec `bytes.fromhex` de Python, des espaces ASCII
seuls se décodent en zéro octet. Les extraits mal formés, mais dans les limites, restent disponibles comme métadonnées
d'événement et échouent au décodage; ils ne sont jamais récupérés ni lus automatiquement.
Des événements audio distincts peuvent volontairement répéter un contenu sonore identique. Seule
la livraison répétée du même objet événement est supprimée, lorsque l'identité de l'objet
est disponible, sans être comptée comme un extrait abandonné. Le classement des exemples rendus
tient compte de la présence d'emplacements dans le motif d'origine, même lorsque des valeurs d'exemple sont fournies.

Les fusions protégées rejettent, avant l'écriture, les entiers situés hors de la plage sûre de JavaScript :
la lecture et la fusion ne peuvent donc pas arrondir en silence une valeur de configuration non modifiée.
Cette validation couvre aussi les personas fournis. Les valeurs non finies et les exposants numériques
stockés qui dépassent la capacité des nombres JavaScript sont rejetés, ce qui empêche
leur conversion en `null` lors de la sérialisation.
Représentez les grands identifiants sous forme de chaînes ou utilisez un SDK qui gère les entiers sans perte.

## Listes d'intentions adaptées aux paramètres régionaux

*Disponible depuis la version `0.6.1`.*

Les listes de phrases utilisent les données groupées `thalovant-languages` 0.2.1 et correspondent à Python
0.6.8 avec son extra de listes. La sélection de la langue suit les distances régionales d'OVOS :
une demande pour `fr-CA` peut donc utiliser des paramètres régionaux `fr-FR` enregistrés.

```typescript

const examples = intent.examples("fr-CA", 2, { sentence: true });
const text = asSentence("what time is it", "en-US");
const sample = speakable("play {song}", {}, "en-US");
```

Le rendu en phrase implique le rendu prononçable. Les valeurs d'emplacement explicites l'emportent sur
les exemples régionaux. Les phrases complètes sont classées avant les préfixes et les motifs à emplacements; les exemples
rendus vides ou en double ne comptent pas dans la limite. Sans limite, les exemples bruts
gardent l'ordre d'enregistrement. Pour des paramètres régionaux inconnus, le texte brut et les noms d'emplacements sont conservés.

Les méthodes d'exemples existantes restent disponibles. Utilisez `ListingRules` pour fournir un
instantané complet de données personnalisées ou choisir un rendu brut sans données régionales.
Aucune requête réseau n'est nécessaire pour charger les règles groupées. Consultez le README du dépôt
pour la construction de règles personnalisées et la gestion des erreurs d'expressions régulières.

Les données de listes décrivent maintenant 270 langues, y compris les variantes régionales.
L'espagnol `qué hora es` devient `Qué hora es?`, et le français `coupe le son`
devient `Coupe le son.`. Les langues sans règles, comme `tlh`, restent brutes.

## Sessions gérées et caches d'inventaire

`HubSession` gère une connexion au hub réutilisable. Fournissez une fabrique qui renvoie un
client connecté et qui nettoie toute tentative de connexion échouée ou annulée. Les
abonnements aux événements survivent au remplacement du client. Go et Rust exposent un flux d'événements
persistant; les autres SDK gérés exposent des descripteurs d'abonnement. Fermez la session
lorsque son propriétaire s'arrête; la fermeture attend les opérations acceptées et est définitive.

Les tentatives de connexion en arrière-plan s'espacent de 10, 20, 40, 80, puis 120 secondes.
Les appels au premier plan peuvent réessayer immédiatement. La planification des sondes relève de votre application :
utilisez le délai de sonde indiqué (60 secondes en état retenu, 5 secondes en état hors service).
Le SDK ne rejoue jamais un Ask ou un Emit accepté après une réponse perdue, car un
Ask peut déclencher une action. Le délai d'expiration d'une requête s'applique à l'opération sous-jacente;
l'attente de la session et votre fabrique de connexions ont des budgets distincts.

```ts

const session = new HubSession(connectClient, { warm: false });
try {
  const reply = await session.ask("What is the weather?");
} finally {
  await session.close();
}
```

`Inventory`, `Skill` et `Intent` offrent une vue présentable, distincte de
l'inventaire d'intentions natif du runtime. Les paramètres régionaux inconnus du catalogue restent inconnus;
des phrases observées pour une langue ne prouvent pas que le catalogue la prend en charge. Les exemples choisissent
les paramètres régionaux pris en charge les plus proches. Une limite nulle ou négative renvoie l'ensemble brut des phrases
(Rust utilise zéro pour sa limite non signée). Le JSON du cache inclut l'ordre explicite des langues
des intentions, pour que la sérialisation ne puisse pas changer la langue par défaut des exemples.

`InventoryCache` est facultatif, a une durée de vie (TTL) d'une heure par défaut et signale une absence pour
les données invalides, expirées ou illisibles. Les écritures utilisent des fichiers temporaires privés et uniques
et un remplacement atomique. Sous POSIX, les fichiers de cache sont lisibles et modifiables par leur propriétaire seulement; Windows
utilise les listes de contrôle d'accès (ACL) du répertoire de l'utilisateur. Les clés de cache distinguent le mode, le chemin de l'identité et le nom d'hôte complet et normalisé du hub.
N'utilisez jamais les caches d'inventaire pour stocker des identifiants. Dans les navigateurs, Node utilise un stockage
local à l'origine; ne partagez pas cet espace de noms de stockage entre des utilisateurs sans lien entre eux.

`OriginPreference` accorde à une adresse préférée son propre budget de connexion court et
la met en pause après un échec. Dans les SDK autres que Python, la fabrique doit mettre en œuvre la
liaison à l'adresse dans son propre transport, conserver l'hôte public pour TLS/SNI et
terminer le nettoyage des tentatives échouées avant de rendre la main. Les restrictions du transport et de la plateforme
s'appliquent toujours. Cette fonction ne modifie pas le DNS global et ne désactive pas la validation TLS.

## Revendication des réponses

Une réponse expose `reply.pipelineIds`, `reply.skillIds` et `reply.claimed`.

Les identifiants de pipeline et de compétence sont des chaînes non vides, dans l'ordre de leur première apparition, sans doublons. Une réponse réussie dont les seuls identifiants de pipeline contiennent le texte `fallback` (sensible à la casse) est signalée comme non revendiquée. Toute étape autre que le repli rend revendiquée une réponse réussie. Les réponses réussies de hubs plus anciens, sans marques d'étape, restent revendiquées. Les réponses en échec ou non traitées ne sont jamais revendiquées. Les marques mal formées qui ne sont pas des chaînes sont ignorées.

Servez-vous-en comme d'un indice pour décider de poursuivre ou non une conversation. Le texte de repli et l'état de réussite existant sont conservés. Ces marques ne constituent pas une identité de pair vérifiée et ne doivent pas autoriser d'actions.
