Documentación
Cree un cliente asistente de transmisión
Abra un turno, represente sus eventos reales, responda solicitudes pendientes y recupere la conversación después de una caída de conexión.
Traducido automáticamente del inglés. Ver el original en inglés
Conéctate al sidecar en marcha
Un cliente asistente de transmisión se conecta al mismo sidecar local que el escritorio. Obtenga su URL de loopback asignada y el token de portador de la aplicación actual de la conexión con la que se está integrando. Un puerto guardado del lanzamiento de ayer no es una dirección duradera: el sidecar solicita un puerto efímero cada vez que se inicia. Un token de sesión de cuenta, un token de emparejamiento complementario y un token de puente de acción con alcance tienen propósitos diferentes y no pueden reemplazar al portador de la aplicación en esta ruta de chat.
Antes de enviar un turno, lea GET /api/ai/status, /api/ai/providers, y opcionalmente /api/ai/models?configuredOnly=true. Una credencial configurada significa que hay una clave presente; no prueba que el proveedor aceptará la solicitud o que un punto final admita el modelo seleccionado. El host predeterminado es nikcli integrado. El modo directo puede transmitir una respuesta modelo, pero no proporciona el bucle de herramientas persistente del host integrado ni el programador especializado.
Esta guía utiliza CADCAMFUN_URL y CADCAMFUN_TOKEN como variables del shell del cliente. Proporciónelos a través de su manejo de conexión existente. Ver el Referencia de API de IA para los esquemas exactos y Sidecar para el comportamiento de lanzamiento. Los ejemplos no instalan credenciales ni eligen un modelo en su nombre.
Elija la identidad del proyecto, dibujo y sesión.
El cliente puede suministrar un sessionId en su primer turno y reutilizarlo en turnos posteriores. El servicio asigna ese identificador de cliente a la sesión de host real y verifica el enlace antes de cada solicitud. Conserve la identidad del evento/sesión como estado de la conversación, en lugar de crear un identificador diferente cada vez que la interfaz de usuario se vuelva a conectar.
un verdadero projectId le da al turno acceso a los recursos permitidos de ese proyecto. Un opcional drawingId debe pertenecer a ese proyecto; vincula las herramientas del turno a ese dibujo. No se puede nombrar un dibujo sin su proyecto. Se rechaza una sesión que ya pertenece a otra cuenta o proyecto. Estas son comprobaciones de admisión antes de que el modelo pueda ejecutar las herramientas del proyecto, no sugerencias en un mensaje del sistema.
Para una conversación explicativa general, omita el proyecto y el dibujo. La aplicación utiliza su contexto predeterminado. Para trabajos de geometría, lleve los ID de proyecto/dibujo seleccionados reales a través de chat, recuperación de estado, cola, cancelación y solicitudes de control. Un cambio de cuenta no debe adoptar silenciosamente la sesión activa de la cuenta anterior.
Enviar un turno de transmisión
POST /api/ai/chat/stream devuelve un cuerpo SSE. Requiere un mensaje que no esté en blanco, aunque el esquema de transporte hace que varios campos sean opcionales para que los controladores puedan devolver mensajes de validación legibles. La siguiente pregunta general deshabilita la inyección de contexto almacenado, lo que la hace adecuada para verificar el transporte independientemente de la geometría del proyecto.
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" Agregar projectId y drawingId para trabajar en un dibujo específico. mode acepta general, cad, cam, gcode o toolpath. A model el selector puede contener proveedor, modelo, nivel y variante; Los niveles son rápidos, equilibrados o mejores. Descubra los ID de proveedores/modelos del catálogo en ejecución en lugar de codificar una suposición sobre qué proveedor está disponible.
permissionMode acepta aprobar, preguntar o auto. Los grupos de herramientas destructivas conservan la aprobación humana en todos los modos. El agent el campo selecciona una función de asistente configurada; leer GET /api/ai/agents en lugar de adivinar los ID de los roles.
Leer marcos de datos SSE completos
La ruta utiliza el modo de datos SSE de Effect: cada carga útil de datos SSE es un registro de evento JSON con un type discriminado. Un fragmento de red no es un límite de evento. Los caracteres JSON, finales de línea y UTF-8 se pueden dividir entre lecturas. Acumule texto decodificado y analice solo fotogramas completos, uniendo varias líneas de datos cuando estén presentes.
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()
}
} Este lector demuestra el encuadre; un cliente de producción también debe validar cada evento con el contrato compartido o utilizar el cliente de transmisión generado con tipo. Ignora intencionalmente los campos de control/comentario de SSE porque el estado de la aplicación de este punto final se encuentra en registros de datos JSON. Liberar un flujo de cliente es una limpieza del transporte, no una solicitud explícita para cancelar el trabajo del lado del servidor.
Renderizar el progreso sin duplicar contenido
Inicie la búsqueda con el mismo portador y cuerpo JSON que se muestra arriba, luego repita el lector. Añadir delta.text a texto de respuesta visible y ruta reasoning.text a su propia visión. A done el evento lleva el texto de respuesta final; reemplace o concilie la respuesta acumulada con ese valor final en lugar de agregar la respuesta completa nuevamente.
| Eventos | Estado para mantener |
|---|---|
turn, session_status, step | Estado de ocupado/reintento y paso actual; a un paso de finalización le puede seguir otro paso en el mismo turno. |
tool_call, tool_progress, tool_result | Un registro por callId con nombre, entrada, progreso del argumento, salida y éxito; El agente identifica a un especialista cuando está presente. |
tool_diagnostic | Hallazgos de argumento/ejecución/parcial/verificación, gravedad, texto de recuperación y mutación opcionalAplicada. |
todo, goal | Reemplace la instantánea del plan/meta actual en lugar de agregar copias repetidas. |
retry, compaction, compacted | Explique el trabajo continuo conservando el texto actual de la conversación y la identidad de la sesión. |
heartbeat | Sin contenido visible; el servidor envía uno cada cinco segundos mientras la transmisión fuente permanece activa. |
Correlacionar llamadas a herramientas y resultados mediante callId, no su posición en la matriz. Una herramienta fallida puede devolver diagnósticos útiles mientras el asistente continúa. Un error del proveedor después de una herramienta de edición exitosa no revierte ese cambio de geometría.
Responder permisos y preguntas.
A permission_request incluye sessionId, requestId, permiso y patrones. Presentar la solicitud a la persona y enviar la decisión seleccionada a POST /api/ai/permission/reply. La acción de control de sesión equivalente es reply_permission. El servidor verifica que la solicitud aún esté pendiente en la conversación o en una sesión infantil admitida.
{
"sessionId": "inspection-demo",
"requestId": "request-id-from-the-event",
"decision": "once"
} La decisión es una vez, siempre o rechazar. Es una decisión de permiso del servidor, distinta de un argumento de acción como confirmar. Un pendiente question_request contiene una matriz de preguntas ordenadas. Responder a través de /api/ai/sessions/control con acción answer_question, el ID de la solicitud y una matriz de respuestas por pregunta; rechazarlo con reject_question.
{
"sessionId": "inspection-demo",
"action": "answer_question",
"requestId": "question-id-from-the-event",
"answers": [["The selected option label"]]
} Cada respuesta no debe estar vacía. Una pregunta de opción única rechaza múltiples respuestas y custom=false solo permite etiquetas enumeradas. Conserve las etiquetas exactas y el orden de las preguntas. Si el servidor dice que la solicitud ya no está pendiente, actualice los controles en lugar de publicar repetidamente la respuesta anterior.
Poner en cola, dirigir y detener explícitamente el trabajo
Usar POST /api/ai/chat/queue para un mensaje enviado mientras funciona el turno. Se necesitan ID de sesión, mensaje, ID de proyecto opcional y entrega. queue es el valor predeterminado y espera el siguiente paso seguro; steer pide al anfitrión que lo incorpore prontamente. A user_message el evento indica que el mensaje ha entrado en la conversación.
Usar POST /api/ai/chat/abort con la identidad de la conversación cuando la persona presiona Detener. Este detiene el turno y se une a sus trabajadores asociados. No implemente Stop únicamente cancelando la recuperación: perder el flujo HTTP no es la misma operación. Luego, una sesión puede aceptar otro turno sin crear una nueva identidad de proyecto.
POST /api/ai/sessions/remove elimina la sesión del anfitrión después de cancelar y unirse a sus hijos. Devuelve una bandera eliminada. Eliminar una conversación no es una reversión de las ediciones del proyecto realizadas por sus herramientas; revisarlos o restaurarlos a través de las API del proyecto/historial cuando sea necesario.
Recuperarse después de perder la transmisión
Cuando el lector termina sin un evento final, trate la finalización como algo desconocido. Llamar POST /api/ai/sessions/state con el mismo ID de sesión y ID de proyecto. La respuesta contiene ocupado y texto, con herramientas, tareas pendientes y eventos de diagnóstico, uso y tareas recuperados opcionales. Reconstruya la interfaz de usuario a partir de esta instantánea; La reproducción de eventos de herramientas recuperados no los ejecuta nuevamente.
Entonces solicita /api/ai/sessions/control con estado de acción para inspeccionar permisos/preguntas pendientes, tareas de los trabajadores, objetivos y política de contexto. Un turno ocupado esperando una pregunta sin respuesta necesita una respuesta, no una segunda copia de la pregunta original. Una sesión reportada como inactiva con texto acumulado se puede mostrar como salida recuperada aunque la conexión TCP original desaparezca.
Mantenga una distinción entre falla de red, un evento de error de configuración/proveedor escrito, aborto explícito y eliminación de sesión. Vuelva a intentar la solicitud del modelo solo después de comprobar qué hizo realmente el primer giro. En particular, lea el dibujo actual después de un diagnóstico de mutación parcial o de un trabajador de edición interrumpido.
Dentro del servidor, una conexión de modelo interrumpida se reanuda hasta cinco veces con una interrupción de 2 segundos que se duplica hasta 30 segundos. Un cliente que se vuelve a conectar a la transmisión aún debe leer el estado de la sesión, porque esos currículums pueden finalizar el trabajo que el cliente no vio la transmisión.
Contexto, archivos adjuntos y compactación.
includeContext: false deshabilita la adquisición del contexto almacenado; Aún se puede proporcionar texto de contexto explícito para ese turno. El ensamblaje normal ordena los requisitos permanentes, las observaciones actuales, las instantáneas de la interfaz de usuario, la memoria y las referencias de soporte, los deduplica y aplica un presupuesto agregado de 24,000 caracteres. Se informan los bloques omitidos/truncados; este presupuesto no es la ventana de token del proveedor.
Los archivos adjuntos incluyen nombre, MIME opcional, datos y codificación de texto/base64 opcional. Hay como máximo cinco por solicitud. Las imágenes están limitadas a 5 MiB cada una, los archivos PDF a 10 MiB y el texto a 2,000,000 caracteres; El texto corto de hasta 6,000 caracteres también aparece en línea. El texto retenido para las herramientas tiene un alcance de proyecto en la memoria, por lo que volver a conectarse es diferente a reiniciar el servicio. Los archivos no compatibles se rechazan con los motivos.
El contexto de la acción de control solicita un desglose detallado de los tokens; El sondeo de estado ordinario evita el trabajo del tokenizador. El pacto de acción le pide al anfitrión que resuma el material de conversación más antiguo. La consolidación de memoria es una operación de host independiente. Conserve las solicitudes de dibujo y fabricación de origen a través de sus propias API en lugar de depender de una conversación compacta para reproducir la geometría exacta.
Separe los errores de transporte de los resultados del dominio
HTTP 401 significa que la solicitud no pasó la autorización del portador. Un 400 antes de la transmisión comúnmente significa un mensaje vacío, una identidad de sesión inaccesible, una combinación de proyecto/dibujo no válida o un cuerpo con formato incorrecto. Una vez que se establece una secuencia, el controlador puede emitir un evento de error con configuración, proveedor, abortado o clasificación interna; manejar ese evento incluso cuando el estado de la respuesta fuera 200.
Un resultado de herramienta con ok=false es un resultado de herramienta, no necesariamente una secuencia fallida. Mostrar su salida y diagnóstico para que el asistente o persona pueda recuperarse. Una cola de trabajadores también puede estar llena: el planificador permite cuatro tareas activas y dieciséis en espera. Un lector interrumpido puede recuperarse en unos intentos limitados; el trabajo de edición se trata como incierto en lugar de reproducirse automáticamente.
Lea los contratos ejecutables en packages/api/src/groups/ai.ts y ai-events.ts; admisión y comportamiento del cable en apps/sidecar/src/http/ai.ts; mapeo de sesiones en packages/ai/src/service.ts; validación de preguntas en nikcli/session-controls.ts; sincronización de latidos del corazón en heartbeat.ts. Estas fuentes definen qué resultado debe presentar el cliente, independientemente de la prosa del asistente.
Referencia del evento
Cada ESS data: frame es un objeto JSON con un type y un sessionId. Analizar sólo fotogramas completos; una trama puede llegar dividida en fragmentos de red.
| tipo | Campos clave | como manejar |
|---|---|---|
turn | phase: start, recovering, continuing, finish; opcional reason | Mostrar estado de progreso. recovering significa que se está reanudando la conexión con el modelo. |
session_status | status: busy, idle, retry | Habilite o deshabilite el botón de enviar y la cola. |
user_message | el mensaje de usuario almacenado | Conciliar la interfaz de usuario optimista. |
delta | text | Añadir a la respuesta visible. |
reasoning | texto de razonamiento | Opcional; mostrar en un área plegable. |
step, todo, goal | plan de progreso | Representar la lista de tareas o el plan. |
tool_call | callId, herramienta, argumentos, opcional agent | Muestra qué herramienta se utiliza y para qué especialista. |
tool_progress | callId, progreso | Actualizar una herramienta de larga duración. |
tool_result | callId, resultado | Mostrar el resultado; emparejarlo por callId. |
tool_diagnostic | detalles incluyendo mutationApplied | Decide si el modelo cambió. Léelo antes de volver a intentar editar. |
permission_request / permission_resolved / permission_blocked | requestId; responder once, always, reject | Pregúntele al usuario, entonces POST /api/ai/permission/reply. |
question_request / question_resolved | datos de la pregunta | Presenta la pregunta y envía la respuesta. |
context_snapshot, compaction, compacted | estado de contexto | Informar al usuario que se resumió el historial anterior. |
retry | attempt, message | Mostrar un aviso transitorio; no reenvíe el mensaje. |
done | text, opcional usage, opcional recovered | Texto final del turno. Reemplace los deltas acumulados con él. |
error | code: provider, config, aborted, internal; message | Finalice el turno en la UI con el mensaje; config por lo general, significa que faltan credenciales. |
heartbeat | ninguno | Mantener vivo aproximadamente cada 5 segundos; ignorar. |
Un completo cliente de streaming
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 se cae la conexión, no vuelvas a enviar el mensaje. Lea la sesión con POST /api/ai/sessions/state, porque es posible que el servidor haya reanudado la conexión del modelo y haya terminado el turno sin ti. Usar /api/ai/chat/abort detener un giro deliberadamente, y /api/ai/chat/queue para agregar un mensaje mientras la sesión está ocupada.
Cómo se ve una transmisión sin un proveedor
Sin ningún proveedor de IA configurado, la transmisión aún se abre y responde con un error escrito, seguido de un latido. Capturado desde un sidecar nuevo:
data: {"type":"error","sessionId":"","code":"config","message":"No AI provider is configured. Add an API key in the AI settings."}
data: {"type":"heartbeat"} - El estado HTTP sigue siendo 200 y el tipo de contenido
text/event-stream: los problemas de configuración son resultados de dominio, entregados como eventos. sessionIdestá vacío porque no se creó ninguna sesión.heartbeatlos marcos mantienen viva la conexión; ignórelos al renderizar y no los cuente como progreso.- Cada cuadro termina con una línea en blanco. Analizar dividiendo en líneas en blanco y uniendo el
data:líneas de un marco, como lo hace el iterador del SDK.
Pon a prueba a tu cliente sin modelo
Apunta a un cliente a un sidecar sin credenciales: ejerce la ruta SSE completa y la rama de error sin costo.