# SDK Swift

> Installez le SDK Swift de Thalovant pour les applications iOS, macOS et Linux qui se connectent à un hub.

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

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

Utilisez le SDK Swift quand votre client fonctionne sur iOS, sur macOS ou avec Swift sous Linux.

Le paquet se résout avec Swift Package Manager à partir du dépôt GitHub public. Swift 5.9 ou plus récent est requis, sur iOS 15 ou plus récent, macOS 12 ou plus récent, ou Linux avec le réseau de Foundation. Le SDK n'a aucune dépendance tierce.

Code source et versions : [thalovant-swift-sdk sur GitHub](https://github.com/thalovant/thalovant-swift-sdk).

## Installation

Ajoutez le paquet à votre `Package.swift` et déclarez une dépendance au produit `ThalovantSDK`.
`from:` accepte cette version et toute version ultérieure inférieure à 1.0; la version
courante figure dans [Versions prises en charge](/fr/developers/sdks/#versions-prises-en-charge).

```swift
dependencies: [
    .package(url: "https://github.com/thalovant/thalovant-swift-sdk", from: "0.11.0"),
]
```

## Première requête

Ce flux se connecte, crée une identité de client et envoie une requête par WSS.

```swift

let api = ThalovantControlPlane()

try await api.login(email: "you@example.com", password: "password")

let result = try await api.createClientIdentity(
    hubId: "hub-id",
    options: CreateClientIdentityOptions(name: "swift-demo-client")
)

let client = try ThalovantClient(identity: result.identity)
do {
    try await client.connect()
    let reply = try await client.ask("Tell me a short clean joke.")
    print(reply.text)
    await client.close()
} catch {
    await client.close()
    throw error
}
```

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

  Gardez `result.identity` secret. Les actions authentifiées de l'API exigent l'accès à l'API dans l'espace de travail. N'inscrivez pas `result.asJSON(includeSecrets: true)` dans les journaux.

## 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 avec la connexion. Sans l'un d'eux, l'API refuse la connexion avec le code HTTP 401 et le code `mfa_required`, exposé dans `ThalovantApiError.errorCode`.

```swift
try await api.login(email: "you@example.com", password: "password", otpCode: "123456")

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

Les valeurs `otpCode` et `recoveryCode` ne sont envoyées que si elles sont fournies.

## Se connecter sans mot de passe

La connexion par appareil demande à votre navigateur d'approuver la connexion : les applications et les outils 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 quand 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 et limité à des portées précises, retourné sous la forme d'un `DeviceLoginResult` typé.

La connexion par appareil exige le SDK 0.1.1 ou plus récent. Approuver la demande exige un forfait payant pour l'espace de travail; un forfait gratuit reçoit le code HTTP 402.

```swift
let token = try await api.loginWithBrowser(options: DeviceLoginOptions(
    scopes: ["hubs:read", "clients:write"],
    clientName: "my-macbook"
))
print(token.tokenId ?? "", token.scopes)
```

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é quand le processus doit démarrer déjà authentifié, par exemple pour des tâches de CI et des services. Créez-en un sur la [page **API Tokens**](/fr/account/api-tokens/) ou avec `loginWithBrowser`; la page affiche les portées, l'expiration et la dernière utilisation, et permet de révoquer le jeton en tout temps.

```swift
let api = ThalovantControlPlane(accessToken: ProcessInfo.processInfo.environment["THALOVANT_API_TOKEN"])
```

## Où le SDK garde ses clés

Le SDK se connecte par WSS. Chaque connexion est chiffrée et authentifiée avant l'envoi de quoi que ce soit. HTTPS et MQTT ne sont toujours pas pris en charge.

Le `ThalovantFileNoiseStore` par défaut garde les clés du SDK et les identités de hub qu'il a enregistrées dans le répertoire Application Support de la plateforme, séparément pour chaque identité. Conservez-le d'un redémarrage à l'autre. Passez `noiseStore:` à `ThalovantClient` ou à `HiveMindWSSTransport` pour utiliser un autre répertoire privé, ou implémentez `ThalovantNoiseStore` pour un stockage adossé au trousseau (Keychain). Utilisez des identités distinctes pour des clients simultanés, et ne supprimez jamais automatiquement une identité de hub enregistrée.

  Le transport WSS a besoin d'une implémentation de Foundation qui prend en charge WebSocket. Les images Linux Swift 5.10 et 6.3.3 standard échouent à établir la connexion WebSocket parce que leur libcurl n'offre pas cette prise en charge. Le SDK est vérifié sur macOS et sous Linux avec Swift 6.3.3 et une libcurl 8.22.0 compatible WebSocket. Utilisez le script `tools/interop/build-websocket-curl.sh` du dépôt ou un autre runtime vérifié qui prend en charge WebSocket.

Utilisez de nouveaux identifiants de requête (request ID) pour Ask et de nouveaux identifiants d'interrogation (query ID) pour Query; les identifiants générés sont la valeur 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 en retard peut encore le porter. Réutilisez un identifiant de session pour le contexte de conversation.

## Protocole de runtime pris en charge

Dans cette version, `ThalovantClient` se connecte au hub par WSS seulement. Le construire avec `hubProtocol: .https` ou `.mqtt` lève `ThalovantUnsupportedProtocolError`. Le choix du point de terminaison privilégie toujours `wss`, puis `https`, puis `mqtt` : les identités créées pour des hubs multiprotocoles continuent donc de fonctionner.

Utilisez le SDK [Python](/fr/developers/sdks/python/), [Node.js](/fr/developers/sdks/node/), [Go](/fr/developers/sdks/go/) ou [Rust](/fr/developers/sdks/rust/) quand le client a besoin dès aujourd'hui du plan de données HTTPS ou MQTT.

Les appels simultanés au runtime partagent une seule tentative de connexion et attendent que la connexion soit authentifiée et prête. Vous pouvez tout de même attendre explicitement `client.connect()` avant de commencer. Annuler un appelant qui s'est joint à la tentative, ou faire expirer son délai, met fin seulement à son attente; annuler l'appelant qui a lancé la connexion, ou faire expirer son délai, ferme cette tentative pour tous ceux qui l'attendent. Gardez le client ouvert pendant ces requêtes, puis attendez `client.close()` quand elles sont terminées.

## Utiliser une identité enregistrée

Les identités peuvent être construites à partir de JSON ou chargées depuis un fichier JSON. Le fichier ne doit pas être lisible par le groupe ni par tous; exécutez d'abord `chmod 600 <path>`.

```swift
let identity = try ThalovantIdentity.fromFile("/path/to/identity.json")
let client = try ThalovantClient(identity: identity)
```

Le document d'identité utilise les mêmes champs en snake_case que ceux retournés par l'API, y compris `access_key`, `password`, `site_id` et les sections facultatives `data_plane_endpoints`, `protocols` et `mqtt`. Un `crypto_key` présent dans un ancien fichier d'identité est encore analysé, puis ignoré.

## Suivre une opération

Les commandes du plan de contrôle qui modifient des données retournent des opérations durables. Interrogez-les avec la fonction typée :

```swift
let operation = try await api.getOperation(id: "operation-id")
print(operation.status)  // .requested, .committed, .applied, .ready, .failed, .timedOut
```

Consultez [Opérations](/fr/developers/operations/) pour le cycle de vie et les conseils sur les nouvelles tentatives.

## Écouter les événements

```swift
let subscription = client.on("speak") { event in
    print(event.displayText)
}
// Later, when the listener is no longer needed:
subscription.close()
```

## 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, dans sa propre session et sans jeton du plan de contrôle. Consultez [Que peut-on demander à mon hub?](/fr/concepts/hub-intents/) pour savoir ce que le hub retourne, 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.1.8, les appels sont `client.intents(languages:options:)`, qui retourne l'inventaire regroupé par compétence, `client.listIntents(lang:options:)`, qui retourne les lignes d'enregistrement, et `client.describeIntent(skillId:intentName:lang:options:)`, qui retourne les définitions derrière une intention.

```swift
let client = try ThalovantClient.fromIdentityFile("_identity.json")
do {
    let inventory = try await client.intents(languages: ["en-us", "fr-fr"])
    print(inventory.source, inventory.languages)
    for skill in inventory.skills {
        for intent in skill.intents {
            print(intent.id, intent.engine, intent.examples(lang: "fr-fr"))
        }
    }
} catch let denied as ThalovantPolicyDeniedError {
    print("refused:", denied.deniedType, "allowed:", denied.allowed)
}
await client.close()
```

`intents(languages:)` interroge `ovos.intent.list` une fois par langue, puis complète les phrases que la liste ne contenait pas, par lots de `defaultDescribeBatchSize` requêtes, pour qu'un hub qui compte de nombreuses intentions ne reçoive jamais toute la rafale d'un coup. `IntentInventoryOptions` porte l'échéance et les options : `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 plus complètes jusqu'à huit mots; `phrasesFor(_:)` les retourne toutes, et l'inventaire est `Codable` : `asJSON()` écrit donc le même document que les autres SDK. Les étiquettes de langue se comparent sans tenir compte de la casse ni de `_` et `-` : `fr-fr` et `fr_FR` forment donc une seule requête.

Un hub qui refuse une requête lève immédiatement `ThalovantPolicyDeniedError` avec `deniedType`, `code`, `reason` et `allowed`. Avec la valeur par défaut `IntentInventoryOptions(fallback: true)`, un `ovos.intent.list` refusé retourne plutôt les noms seulement : `inventory.source` vaut `.engineManifests`, `inventory.denied` nomme la requête refusée et `inventory.hasPhrases` est faux; vérifiez donc cet indicateur avant de promettre des phrases. Un hub qui accepte la liste et y répond avec `ok: false` est un cas différent : depuis la 0.1.9, cela lève `ThalovantRuntimeError` avec le texte d'erreur du hub, parce qu'une liste en échec n'est pas un hub vide. `describeIntent` continue de retourner une liste vide pour la même réponse, qui est une vraie réponse : le hub ne connaît pas cet enregistrement.

L'inventaire par défaut se replie aussi quand `ovos.intent.list` reste silencieux. Désactivez le repli pour propager ce délai d'expiration de la liste. Le marqueur `denied` nomme la requête qui a déclenché le repli; il n'établit pas un refus de politique. `listFallbacks()` expose les gestionnaires de repli enregistrés, et l'inventaire inclut `fallbacksKnown` et `mayAnswer(_:)`.

La sonde facultative des gestionnaires de repli prend au plus 1,5 seconde pour la connexion, l'envoi et l'attente de la réponse. Une sonde refusée, silencieuse ou explicitement en échec, ou une enveloppe de réponse malformée, laisse la découverte inconnue. Les tableaux acceptés conservent les gestionnaires utilisables et ignorent les lignes invalides. Des gestionnaires connus comme vides et une découverte inconnue sont deux états différents : la disponibilité d'une langue est fausse seulement quand aucune intention activée n'a de phrases pour cette langue et qu'une sonde réussie ne signale aucun gestionnaire de repli. La présence d'un gestionnaire de repli ne garantit pas qu'il peut répondre dans toutes les langues.

## Fonctions du runtime et échéances

Une conversation partage la connexion, l'identifiant de session, la langue et le contexte du client. Chaque requête reçoit un nouvel identifiant de requête :

```swift
let conversation = client.conversation(sessionId: "checkout", lang: "en-us")
let reply = try await conversation.query("Tell me a short clean joke.", timeout: 12)
print(reply.text)
```

`query` n'accepte que l'identifiant de query correspondant et collecte les réponses query/cascade jusqu'à `hive.query.complete`. Les échecs d'intention non bloquants permettent de la parole ultérieure; un refus de politique et l'expiration du délai de la query sont définitifs et conservent la parole partielle dans une réponse en échec. Son échéance couvre la connexion, l'envoi mis en file d'attente et la collecte. Les requêtes ne sont jamais rejouées automatiquement.

Utilisez `waitForEvent` pour un événement et `listen` pour un `AsyncThrowingStream`. Appeler `listen` démarre l'abonnement. Son tampon de 64 événements signale les débordements; l'expiration du délai, le nombre d'événements, l'annulation de la tâche et la perte du transport libèrent les abonnements. `sendAction` et `sendCode` préservent les métadonnées des entrées structurées. `connectionInfo`, `connectWithInfo`, `healthcheck` et `doctor` rapportent l'état local du transport authentifié, sans garantir que chaque compétence distante est en bon état.

`connect(timeout:)` utilise un seul budget pour l'établissement du socket et la disponibilité authentifiée, avec une valeur par défaut de six secondes. Une annulation survenue avant qu'une écriture en file d'attente soit acceptée fait sauter cette écriture. Une fois l'écriture acceptée, l'annulation par l'appelant arrête l'attente, tandis que l'écriture chiffrée garde sa place dans la file d'envoi, sous une échéance indépendante de 20 secondes. Un échec physique d'écriture ou une expiration retire la connexion touchée; le nettoyage en garde la propriété jusqu'à ce que l'écriture s'arrête réellement. Tenez compte des exigences de plateforme ci-dessus pour choisir un runtime Swift.

## Sécurité HTTP du plan de contrôle

Les redirections du plan de contrôle sont refusées. Les requêtes qui transportent des identifiants ou un corps JSON exigent HTTPS, sauf pour les points de terminaison de développement HTTP explicites `localhost`, `127.0.0.1` et `[::1]`. Les en-têtes injectés, les témoins (cookies), les identifiants stockés et les rappels de confiance personnalisés exigent aussi un transport sécurisé. Les lectures anonymes, sans ces identifiants ni corps, peuvent utiliser HTTP. Les URL qui contiennent des informations d'utilisateur (userinfo) sont refusées avant toute E/S.

Quand une `URLSession` est fournie, le SDK crée sa propre session à partir de cette configuration et garde le contrôle des décisions de redirection. Il transmet les rappels de défi d'authentification au délégué fourni pour la gestion de la confiance personnalisée. Les implémentations de protocoles d'URL fournies restent du code d'application de confiance. La connexion par appareil n'ouvre que des URL de vérification HTTP(S) sans informations d'utilisateur ni caractères de contrôle.

## Fonctions utiles

  
    Envoyez une requête et recevez une réponse normalisée.
  
  
    Observez les événements du bus du hub par nom, avec un abonnement qu'on peut fermer.
  
  
    Interrogez une commande durable du plan de contrôle avec un statut typé.
  
  
    Découvrez les hubs publics avant la connexion.
  

Pour des recettes dans plusieurs langages, consultez [Fonctions des SDK](/fr/developers/sdk-functions/).

## Provisionner des hubs

Depuis le SDK 0.1.3, 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.

```swift
let group = try await api.createRuntimeGroup(["name": "kiosks"])
let hub = try await api.createHub([
    "name": "joke-garden",
    "runtimeGroupId": .string(group["id"]?.stringValue ?? ""),
    "spec": .object([:]),
])

let current = try await api.getHub(hub["id"]?.stringValue ?? "")
_ = try await api.updateHub(
    current["id"]?.stringValue ?? "",
    ["active": false],
    etag: current["etag"]?.stringValue ?? "",
)
```

`updateHub` et `deleteHub` prennent l'etag actuel du hub comme argument obligatoire. Consultez [Provisionner des hubs](/fr/developers/provisioning/) pour le flux complet, les champs immuables et le tableau des erreurs.

## Problèmes courants

| Symptôme | Vérification |
| --- | --- |
| `ThalovantApiError` avec `Missing Thalovant API access token` | Appelez `api.login(...)` ou `api.loginWithBrowser(...)` avant les actions privées de l'API, ou passez `accessToken:` à `ThalovantControlPlane`. |
| HTTP 401 avec `errorCode` `mfa_required` | Passez `otpCode` ou `recoveryCode` à `api.login(...)`. |
| 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 par l'API. |
| `ThalovantUnsupportedProtocolError` | Le hub n'expose pas WSS, ou le code a demandé `https` ou `mqtt`, que cette version ne prend pas en charge pour se connecter. |
| `ThalovantIdentityError` | Le fichier d'identité est malformé ou lisible par d'autres utilisateurs. |
## Gérer les compétences par 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. **Chaque hub qui partage ce groupe de runtime est touché.** 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 servis par 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 retourne 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 retourne l'enveloppe JSON de l'API avec des entrées `event` et `operation`
de la plus récente à la plus ancienne, en préservant les champs acteur/version qui peuvent être nuls. La limite va de 1 à 200.

Utilisez `HubSkillWaitOptions` pour choisir d'attendre la fin de l'opération (120 secondes, avec une interrogation toutes
les deux secondes par défaut). Pour les appels sensibles à l'annulation, soumettez d'abord sans
attendre, conservez la réponse acceptée, puis appelez `waitForHubSkillOperation`.
Annuler l'attente n'annule pas l'opération acceptée par le serveur. En cas d'échec de l'interrogation,
l'erreur conserve l'identifiant de l'opération; examinez cette opération au lieu de répéter
l'écriture. L'échéance d'attente empêche de nouvelles interrogations, mais n'annule pas une requête
déjà en cours.

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

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 nulles (zéro/zéro).
Le hub valide les indications de langue en fonction des langues configurées.

Les réponses exposent la langue déclarée, les événements de parole et d'audio ordonnés et le
nombre de médias abandonnés. Les extraits audio intégrés des 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 blancs ASCII; il ne récupère jamais une URL ou un chemin de fichier fourni par une compétence.
La lecture relève de l'application. Toute fonction `play` ou `Play` ci-dessous appartient à votre application.

```swift
let location = buildLocation(city: "Montréal", country: "CA")
let reply = try await client.askWithHints("Quel temps fait-il ?", sttLang: "fr-ca", location: location)
for event in reply.mediaEvents where event.isAudio { play(try event.audioBytes()) }
let examples = intent.examples(lang: "en-us", speakable: true)
try await api.updateRuntimeGroupConfig(groupId, config: delta)
// Explicit full replacement:
try await api.replaceRuntimeGroupConfig(groupId, config: fullConfig)
```

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 retourne une `revision` valide
et dont le PUT de configuration vérifie `expected_revision`. Le SDK relit et
réapplique le delta d'origine seulement après un HTTP 412, avec au plus trois tentatives.
Les tableaux et les valeurs scalaires remplacent; les objets fusionnent récursivement. Les personas ne sont remplacés
que s'ils sont fournis explicitement. Les échecs de connexion, les redirections, les autres statuts
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 rédacteurs.

Utilisez l'opération de remplacement explicite montrée ci-dessus quand un remplacement complet
est voulu, 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 des variantes et
substituent les emplacements (slots) fournis par l'appelant tout en conservant la priorité des phrases complètes.

Les limites audio utilisent des bornes supérieures basées sur la longueur encodée avant le décodage : les
espaces blancs de mise en forme consomment donc aussi le budget. Comme avec `bytes.fromhex` de Python, des espaces blancs ASCII
seuls se décodent en zéro octet. Les extraits malformés bornés 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 répéter volontairement un contenu sonore identique. Seule
la livraison répétée du même objet d'événement est supprimée, là où l'identité des objets
est disponible, sans la compter comme un extrait abandonné. Le classement des exemples rendus
utilise la présence d'emplacements dans le motif d'origine, même quand des valeurs d'exemple sont fournies.

Les fusions protégées préservent les valeurs `Int` natives. Les valeurs à virgule flottante dans la configuration,
les personas ou la configuration stockée doivent être finies et se situer dans l'intervalle
±9 007 199 254 740 991. Les valeurs plus grandes échouent avant l'écriture, car le décodage pourrait
avoir arrondi un entier au-delà du stockage natif. Utilisez des identifiants sous forme de chaîne pour les
entiers qui dépassent le stockage natif.

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

*Disponible depuis la `0.6.1`.*

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

```swift
let examples = intent.examplesWithListing(lang: "fr-CA", sentence: true)
let text = asSentence("what time is it", lang: "en-US")
let sample = speakableWithLanguage("play {song}", lang: "en-US")
```

Le rendu en phrase implique le rendu prononçable. Les valeurs d'emplacement explicites remplacent
les exemples régionaux. Les phrases complètes passent avant les préfixes et les motifs à emplacements; les exemples
rendus vides ou en double ne comptent pas dans la limite. Les exemples bruts sans limite
gardent l'ordre d'enregistrement. Les paramètres régionaux inconnus conservent le texte brut et les noms d'emplacements.

Les méthodes d'exemples existantes restent disponibles. Utilisez `ListingRules` pour fournir un
instantané complet de données personnalisées ou pour choisir un rendu brut sans données régionales.
Aucune requête réseau n'est nécessaire pour charger les règles intégré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 liste décrivent maintenant 270 langues, y compris les variantes régionales.
En espagnol, `qué hora es` devient `Qué hora es?`, et en 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` possède une connexion de hub réutilisable. Fournissez une fabrique qui retourne un
client connecté et qui nettoie une 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
quand 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 attendent 10, 20, 40, 80, puis 120 secondes entre elles.
Les appels au premier plan peuvent tenter immédiatement. Votre application gère la planification des sondes :
utilisez le délai de sonde indiqué (60 secondes quand la connexion est retenue, 5 secondes quand elle est coupée).
Le SDK ne rejoue jamais un Ask ou un Emit accepté après la perte d'une réponse, parce qu'un
Ask peut déclencher une action. Le délai d'attente d'une requête s'applique à l'opération sous-jacente;
l'attente de la session et votre fabrique de connexion ont des budgets distincts.

```swift
let session = HubSession(connect: connectClient, warm: false)
do {
    let reply = try await session.ask("What is the weather?")
    await session.close()
} catch {
    await session.close()
    throw error
}
```

`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 non positive retourne 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, utilise par défaut une durée de vie (TTL) d'une heure et signale une absence dans le cache 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 ACL du répertoire de l'utilisateur. Les clés de cache séparent 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.

`OriginPreference` donne à une adresse privilégié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 implémenter la
liaison à l'adresse sur son propre transport, conserver l'hôte public pour TLS/SNI et
terminer le nettoyage des tentatives échouées avant de retourner. 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 première apparition, sans doublons. Une réponse réussie dont les identifiants de pipeline contiennent tous le texte `fallback` (sensible à la casse) est signalée comme non revendiquée. Toute étape autre qu'un repli rend une réponse réussie revendiquée. 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 malformées qui ne sont pas des chaînes sont ignorées.

Utilisez cette information comme indice pour décider de poursuivre ou non une conversation. Le texte de repli et le statut 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.
