Documentation
Créer un client d'assistant de streaming
Ouvrez un tour, restituez ses événements réels, répondez aux demandes en attente et récupérez la conversation après une interruption de connexion.
Traduit automatiquement de l'anglais. Voir l'original en anglais
Connectez-vous au sidecar en cours d'exécution
Un client d'assistant de streaming se connecte au même sidecar local que le bureau. Obtenez l'URL de bouclage qui lui est attribuée et le jeton de support d'application actuel à partir de la connexion avec laquelle vous effectuez l'intégration. Un port enregistré lors du lancement d'hier n'est pas une adresse durable : le sidecar demande un port éphémère à chaque démarrage. Un jeton de session de compte, un jeton de couplage de compagnon et un jeton de pont d'action étendu ont des objectifs différents et ne peuvent pas remplacer le porteur d'application sur cette route de discussion.
Avant de soumettre un tour, lisez GET /api/ai/status, /api/ai/providers, et éventuellement /api/ai/models?configuredOnly=true. Un identifiant configuré signifie qu'une clé est présente ; cela ne prouve pas que le fournisseur acceptera la demande ou qu'un point de terminaison prend en charge le modèle sélectionné. L'hôte par défaut est nikcli intégré. Le mode direct peut diffuser une réponse modèle mais ne fournit pas la boucle d'outils persistante ou le planificateur spécialisé de l'hôte intégré.
Ce guide utilise CADCAMFUN_URL et CADCAMFUN_TOKEN en tant que variables client-shell. Fournissez-les via votre gestion de connexion existante. Voir le Référence de l'API IA pour les schémas exacts et Sidecar pour le comportement de lancement. Les exemples n'installent pas d'informations d'identification et ne choisissent pas de modèle en votre nom.
Choisissez l'identité du projet, du dessin et de la session
Le client peut fournir un sessionId lors de son premier tour et réutilisez-le lors des tours suivants. Le service mappe cet identifiant client à la session hôte réelle et vérifie la liaison avant chaque invite. Conservez l'identité de l'événement/de la session comme état de conversation, plutôt que de créer un identifiant différent à chaque fois que l'interface utilisateur se reconnecte.
Un vrai projectId donne à son tour l'accès aux ressources autorisées de ce projet. Un facultatif drawingId doit appartenir à ce projet ; il lie les outils du tour à ce dessin. Un dessin ne peut pas être nommé sans son projet. Une session déjà détenue par un autre compte ou projet est refusée. Il s'agit de contrôles d'admission avant que le modèle puisse exécuter les outils du projet, et non d'indications dans une invite système.
Pour une conversation explicative générale, omettez le projet et le dessin. L'application utilise son contexte par défaut. Pour les travaux de géométrie, transférez les ID de projet/dessin actuellement sélectionnés via le chat, la récupération d'état, la file d'attente, l'abandon et les demandes de contrôle. Un changement de compte ne doit pas adopter silencieusement la session active du compte précédent.
Soumettre un tour de streaming
POST /api/ai/chat/stream renvoie un corps SSE. Il nécessite un message non vide même si le schéma de transport rend plusieurs champs facultatifs afin que les gestionnaires puissent renvoyer des messages de validation lisibles. La question générale suivante désactive l'injection de contexte stocké, ce qui la rend adaptée à la vérification du transport indépendamment de la géométrie du projet.
curl --no-buffer --fail-with-body \
-H "Authorization: Bearer $CADCAMFUN_TOKEN" \
-H "Content-Type: application/json" \
--data '{"message":"Explain the difference between G0 and G1.","sessionId":"inspection-demo","includeContext":false,"mode":"gcode"}' \
"$CADCAMFUN_URL/api/ai/chat/stream" Ajouter projectId et drawingId travailler sur un dessin précis. mode accepte général, cad, cam, gcode ou parcours d'outil. UN model le sélecteur peut contenir un fournisseur, un modèle, un niveau et une variante ; les niveaux sont rapides, équilibrés ou meilleurs. Découvrez les ID de fournisseur/modèle à partir du catalogue en cours d'exécution au lieu de coder en dur une hypothèse sur le fournisseur disponible.
permissionMode accepte l'approbation, la demande ou l'auto. Les groupes d’outils destructeurs conservent l’approbation humaine dans tous les modes. Le agent le champ sélectionne un rôle d'assistant configuré ; lire GET /api/ai/agents plutôt que de deviner les identifiants de rôle.
Lire des trames de données SSE complètes
La route utilise le mode de données SSE d'Effet : chaque charge utile de données SSE est un enregistrement d'événement JSON avec un type discriminateur. Un fragment de réseau n'est pas une limite d'événement. Les caractères JSON, fins de ligne et UTF-8 peuvent être répartis entre les lectures. Accumulez le texte décodé et analysez uniquement les images complètes, en joignant plusieurs lignes de données lorsqu'elles sont présentes.
async function* events(response: Response): AsyncGenerator<Record<string, unknown>> {
if (!response.ok) throw new Error(await response.text())
if (!response.body) throw new Error("The response has no stream")
const reader = response.body.getReader()
const decoder = new TextDecoder()
let pending = ""
try {
while (true) {
const next = await reader.read()
pending += decoder.decode(next.value, { stream: !next.done })
let boundary: RegExpExecArray | null
while ((boundary = /\r?\n\r?\n/.exec(pending)) !== null) {
const frame = pending.slice(0, boundary.index)
pending = pending.slice(boundary.index + boundary[0].length)
const data = frame.split(/\r?\n/)
.filter((line) => line.startsWith("data:"))
.map((line) => line.slice(5).replace(/^ /, "")).join("\n")
if (!data) continue
const event: unknown = JSON.parse(data)
if (!event || typeof event !== "object" || !("type" in event)) {
throw new Error("Invalid assistant event")
}
yield event as Record<string, unknown>
}
if (next.done) break
}
} finally {
await reader.cancel().catch(() => {})
reader.releaseLock()
}
} Ce lecteur démontre le cadrage ; un client de production doit également valider chaque événement par rapport au contrat partagé ou utiliser le client de streaming généré par saisie. Il ignore intentionnellement les champs de commentaires/contrôle SSE, car l'état d'application de ce point de terminaison réside dans les enregistrements de données JSON. La libération d'un flux client constitue un nettoyage du transport, et non une demande explicite d'abandon du travail côté serveur.
Progression du rendu sans dupliquer le contenu
Démarrez la récupération avec le même support et le même corps JSON indiqués ci-dessus, puis itérez le lecteur. Ajouter delta.text pour afficher le texte de la réponse et l'itinéraire reasoning.text à son propre point de vue. UN done l'événement porte le texte de réponse final ; remplacez ou rapprochez la réponse accumulée avec cette valeur finale au lieu d'ajouter à nouveau la réponse entière.
| Événements | État à maintenir |
|---|---|
turn, session_status, step | État occupé/réessayer et étape en cours ; une étape se terminant peut être suivie d'une autre étape dans le même tour. |
tool_call, tool_progress, tool_result | Un enregistrement par callId avec le nom, l'entrée, la progression de l'argument, la sortie et le succès ; l'agent identifie un spécialiste lorsqu'il est présent. |
tool_diagnostic | Résultats d’argument/exécution/partielle/vérification, gravité, texte de récupération et mutation facultative appliquée. |
todo, goal | Remplacez l'instantané actuel du plan/objectif plutôt que d'ajouter des copies répétées. |
retry, compaction, compacted | Expliquez la poursuite du travail tout en préservant le texte actuel de la conversation et l'identité de la session. |
heartbeat | Aucun contenu visible ; le serveur en envoie un toutes les cinq secondes environ pendant que le flux source reste actif. |
Corréler les appels d'outils et les résultats en callId, pas leur position dans le tableau. Un outil défaillant peut renvoyer des diagnostics utiles pendant que l'assistant continue. Une erreur du fournisseur après un outil d’édition réussi n’annule pas cette modification géométrique.
Répondre aux autorisations et aux questions
A permission_request inclut sessionId, requestId, autorisation et modèles. Présenter la demande à la personne et envoyer la décision retenue à POST /api/ai/permission/reply. L'action de contrôle de session équivalente est reply_permission. Le serveur vérifie que la demande est toujours en attente dans la conversation ou dans une session enfant admise.
{
"sessionId": "inspection-demo",
"requestId": "request-id-from-the-event",
"decision": "once"
} La décision est une fois, toujours ou rejetée. Il s'agit d'une décision d'autorisation du serveur, distincte d'un argument d'action tel que confirmer. Une attente question_request contient un tableau de questions ordonnées. Répondez via /api/ai/sessions/control avec action answer_question, l'ID de la demande et un tableau de réponses par question ; rejetez-le avec reject_question.
{
"sessionId": "inspection-demo",
"action": "answer_question",
"requestId": "question-id-from-the-event",
"answers": [["The selected option label"]]
} Chaque réponse doit être non vide. Une question à choix unique rejette plusieurs réponses et custom=false autorise uniquement les étiquettes répertoriées. Conservez les étiquettes exactes et l’ordre des questions. Si le serveur indique que la demande n'est plus en attente, actualisez les contrôles au lieu de publier à plusieurs reprises l'ancienne réponse.
Mettre en file d'attente, diriger et arrêter explicitement le travail
Utiliser POST /api/ai/chat/queue pour un message soumis pendant que le tour fonctionne. Il prend l'ID de session, le message, l'ID de projet facultatif et la livraison. queue est la valeur par défaut et attend la prochaine étape sûre ; steer demande à l'hôte de l'intégrer rapidement. UN user_message l'événement signale que le message est entré dans la conversation.
Utiliser POST /api/ai/chat/abort avec l'identité de la conversation lorsque la personne appuie sur Stop. Cela arrête le tour et rejoint ses ouvriers associés. N'implémentez pas Stop uniquement en annulant le fetch : perdre le flux HTTP n'est pas la même opération. Une session peut alors accepter un autre tour sans créer une nouvelle identité de projet.
POST /api/ai/sessions/remove supprime la session hôte après avoir annulé et rejoint ses enfants. Il renvoie un indicateur supprimé. La suppression d'une conversation ne constitue pas une restauration des modifications du projet effectuées par ses outils ; examinez-les ou restaurez-les via les API du projet/de l'historique si nécessaire.
Récupérer après avoir perdu le flux
Lorsque le lecteur termine sans événement final, considérez l'achèvement comme inconnu. Appel POST /api/ai/sessions/state avec les mêmes sessionId et projectId. La réponse contient du texte et du texte occupé, avec des événements, des utilisations et des tâches d'outil/tâches/diagnostic récupérés en option. Reconstruisez l'interface utilisateur à partir de cet instantané ; la relecture des événements d'outil récupérés ne les exécute pas à nouveau.
Alors demandez /api/ai/sessions/control avec le statut de l'action pour inspecter les autorisations/questions en attente, les tâches des travailleurs, l'objectif et la politique contextuelle. Un tour occupé attendant une question sans réponse a besoin d’une réponse, pas d’une deuxième copie de l’invite originale. Une session signalée comme inactive avec du texte accumulé peut être affichée comme sortie récupérée même si la connexion TCP d'origine a disparu.
Faites la distinction entre une panne de réseau, un événement d'erreur de fournisseur/configuration, un abandon explicite et une suppression de session. Réessayez la demande de modèle seulement après avoir vérifié ce que le premier tour a réellement fait. En particulier, lisez le dessin actuel après un diagnostic de mutation partielle ou une interruption du travail d'édition.
À l'intérieur du serveur, une connexion de modèle interrompue est reprise jusqu'à cinq fois avec un intervalle de 2 secondes qui double jusqu'à 30 secondes. Un client qui se reconnecte au flux doit toujours lire l'état de la session, car ces reprises peuvent terminer un travail que le client n'a pas vu au flux.
Contexte, pièces jointes et compactage
includeContext: false désactive l'acquisition du contexte stocké ; un texte contextuel explicite peut toujours être fourni pour ce tour. L'assemblage normal commande les exigences permanentes, les observations actuelles, les instantanés de l'interface utilisateur, la mémoire et les références de support, les duplique et applique un budget global de 24,000 caractères. Les blocs omis/tronqués sont signalés ; ce budget n'est pas la fenêtre symbolique du fournisseur.
Les pièces jointes incluent le nom, le MIME facultatif, les données et le codage texte/base64 facultatif. Il y en a au maximum cinq par demande. Les images sont limitées à 5 Mio chacune, les PDF à 10 Mio et le texte à 2,000,000 caractères ; un texte court comportant jusqu'à 6,000 caractères apparaît également en ligne. Le texte conservé pour les outils est limité au projet en mémoire, la reconnexion est donc différente du redémarrage du service. Les fichiers non pris en charge sont rejetés avec raisons.
Le contexte de l'action de contrôle demande une répartition détaillée des jetons ; Un sondage d'état ordinaire évite ce travail de tokenizer. Le compact d'action demande à l'hôte de résumer les anciens éléments de conversation. La consolidation de la mémoire est une opération hôte distincte. Conservez les demandes de dessin et de fabrication source via leurs propres API plutôt que de vous fier à une conversation compactée pour reproduire la géométrie exacte.
Séparez les erreurs de transport des résultats du domaine
HTTP 401 signifie que la demande n'a pas obtenu l'autorisation du porteur. Un 400 avant la diffusion signifie généralement un message vide, une identité de session inaccessible, une combinaison projet/dessin non valide ou un corps mal formé. Une fois qu'un flux est établi, le gestionnaire peut émettre un événement d'erreur avec une classification de configuration, de fournisseur, abandonnée ou interne ; gérer cet événement même lorsque l'état de la réponse était 200.
Un résultat d'outil avec ok=false est un résultat d'outil, pas nécessairement un flux ayant échoué. Affichez sa sortie et son diagnostic afin que l'assistant ou la personne puisse récupérer. Une file d'attente de travailleurs peut également être pleine : le planificateur autorise quatre tâches actives et seize tâches en attente. Un lecteur interrompu peut être récupéré en quelques tentatives limitées ; le travail de montage est traité comme incertain plutôt que automatiquement relu.
Lire les contrats exécutables dans packages/api/src/groups/ai.ts et ai-events.ts; admission et comportement des câbles dans apps/sidecar/src/http/ai.ts; mappage de session dans packages/ai/src/service.ts; validation des questions dans nikcli/session-controls.ts; timing des battements de coeur dans heartbeat.ts. Ces sources définissent le résultat que le client doit présenter, indépendamment de la prose de l'assistant.
Référence de l'événement
Chaque ESS data: frame est un objet JSON avec un type et un sessionId. Analyser uniquement les images complètes ; une trame peut arriver divisée en plusieurs morceaux de réseau.
| taper | Champs clés | Comment gérer |
|---|---|---|
turn | phase: start, recovering, continuing, finish; facultatif reason | Afficher l'état d'avancement. recovering signifie que la connexion au modèle est rétablie. |
session_status | status: busy, idle, retry | Activez ou désactivez le bouton d'envoi et la file d'attente. |
user_message | le message utilisateur stocké | Réconcilier une interface utilisateur optimiste. |
delta | text | Ajouter à la réponse visible. |
reasoning | texte de raisonnement | Facultatif; afficher dans une zone pliable. |
step, todo, goal | planifier les progrès | Rendre la liste des tâches ou le plan. |
tool_call | callId, outil, arguments, facultatif agent | Montrez quel outil fonctionne et pour quel spécialiste. |
tool_progress | callId, progrès | Mettez à jour un outil de longue durée. |
tool_result | callId, résultat | Montrez le résultat ; faites-le correspondre par callId. |
tool_diagnostic | détails, y compris mutationApplied | Décidez si le modèle a changé. Lisez-le avant de réessayer une modification. |
permission_request / permission_resolved / permission_blocked | requestId; répondre once, always, reject | Demandez à l'utilisateur, puis POST /api/ai/permission/reply. |
question_request / question_resolved | données de questions | Présentez la question et envoyez la réponse. |
context_snapshot, compaction, compacted | état de contexte | Informez l’utilisateur que l’historique plus ancien a été résumé. |
retry | attempt, message | Afficher un avis transitoire ; ne renvoyez pas l’invite. |
done | text, facultatif usage, facultatif recovered | Texte final du tour. Remplacez les deltas accumulés par celui-ci. |
error | code: provider, config, aborted, internal; message | Terminez le tour dans l'interface utilisateur avec le message ; config signifie généralement des informations d'identification manquantes. |
heartbeat | aucun | Restez en vie environ toutes les 5 secondes ; ignorer. |
Un client de streaming complet
const base = process.env.CADCAMFUN_URL! // e.g. http://127.0.0.1:53211
const token = process.env.CADCAMFUN_TOKEN!
const response = await fetch(`${base}/api/ai/chat/stream`, {
method: "POST",
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
body: JSON.stringify({ projectId: "p1", message: "Summarise this project", mode: "general" }),
})
if (!response.ok || !response.body) throw new Error(`HTTP ${response.status}`)
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader()
let buffer = "", answer = ""
for (;;) {
const { value, done } = await reader.read()
if (done) break
buffer += value
let end
while ((end = buffer.indexOf("\n\n")) >= 0) { // one SSE frame
const frame = buffer.slice(0, end); buffer = buffer.slice(end + 2)
const data = frame.split("\n").filter((l) => l.startsWith("data:"))
.map((l) => l.slice(5).trimStart()).join("\n")
if (!data) continue
const event = JSON.parse(data)
switch (event.type) {
case "delta": answer += event.text; break
case "permission_request": /* ask, then POST /api/ai/permission/reply */ break
case "done": answer = event.text; console.log(answer); break
case "error": throw new Error(`${event.code}: ${event.message}`)
}
}
} Si la connexion est interrompue, n'envoyez plus le message. Lire la séance avec POST /api/ai/sessions/state, car le serveur a peut-être repris la connexion du modèle et terminé le tour sans vous. Utiliser /api/ai/chat/abort arrêter délibérément un virage, et /api/ai/chat/queue pour ajouter un message pendant que la session est occupée.
À quoi ressemble un flux sans fournisseur
Sans fournisseur d'IA configuré, le flux s'ouvre toujours et répond par une erreur de frappe, suivie d'un battement de cœur. Capturé à partir d'un nouveau sidecar :
data: {"type":"error","sessionId":"","code":"config","message":"No AI provider is configured. Add an API key in the AI settings."}
data: {"type":"heartbeat"} - Le statut HTTP est toujours 200 et le type de contenu
text/event-stream: les problèmes de configuration sont des résultats de domaine, livrés sous forme d'événements. sessionIdest vide car aucune session n'a été créée.heartbeatles cadres maintiennent la connexion active ; ignorez-les lors du rendu et ne les comptez pas comme une progression.- Chaque image se termine par une ligne vierge. Analyser en divisant sur des lignes vides et en joignant le
data:lignes d'une image, comme le fait l'itérateur du SDK.
Testez votre client sans modèle
Dirigez un client vers un sidecar sans informations d’identification : il exerce le chemin SSE complet et la branche d’erreur sans frais.