Vai al contenuto
CADCAMFUN
Documentazione

Documentazione

Costruisci un client di assistente di streaming

Apri un turno, visualizza i suoi eventi reali, rispondi alle richieste in sospeso e ripristina la conversazione dopo una interruzione della connessione.

Tradotto automaticamente dall'inglese. Vedi l'originale in inglese

Connettiti al sidecar in corsa

Un client di assistente di streaming si connette allo stesso sidecar locale del desktop. Ottieni l'URL di loopback assegnato e il token di connessione dell'applicazione corrente dalla connessione con cui ti stai integrando. Un port salvato dal lancio di ieri non è un indirizzo durevole: il sidecar richiede un port effimero ogni volta che si avvia. Un token di sessione dell'account, un token di abbinamento associato e un token action-bridge con ambito hanno scopi diversi e non possono sostituire il portatore dell'applicazione su questo percorso di chat.

Prima di inviare una svolta, leggi GET /api/ai/status, /api/ai/providerse facoltativamente /api/ai/models?configuredOnly=true. Una credenziale configurata significa che è presente una chiave; non dimostra che il fornitore accetterà la richiesta o che un endpoint supporti il modello selezionato. L'host predefinito è nikcli incorporato. La modalità diretta può trasmettere in streaming una risposta modello ma non fornisce il ciclo di strumenti persistente o lo scheduler specializzato dell'host incorporato.

Questa guida utilizza CADCAMFUN_URL e CADCAMFUN_TOKEN come variabili della shell client. Forniscili tramite la gestione della connessione esistente. Vedi il Riferimento API AI per gli schemi esatti e Sidecar per il comportamento di lancio. Gli esempi non installano credenziali né scelgono un modello per tuo conto.

Scegli il progetto, il disegno e l'identità della sessione

Il cliente può fornire a sessionId al suo primo turno e riutilizzarlo nei turni successivi. Il servizio associa l'identificatore client alla sessione host effettiva e controlla l'associazione prima di ogni richiesta. Conserva l'identità dell'evento/sessione come stato della conversazione, anziché creare un identificatore diverso ogni volta che l'interfaccia utente si riconnette.

Un vero projectId dà al turno l'accesso alle risorse consentite di quel progetto. Un facoltativo drawingId deve appartenere a quel progetto; lega gli strumenti del turno a quel disegno. Non è possibile nominare un disegno senza il suo progetto. Una sessione già posseduta da un altro account o progetto viene rifiutata. Si tratta di controlli di ammissione prima che il modello possa eseguire gli strumenti di progetto, non suggerimenti in un prompt di sistema.

Per una conversazione esplicativa generale, omettere progetto e disegno. L'applicazione utilizza il contesto predefinito. Per il lavoro sulla geometria, trasportare gli ID progetto/disegno selezionati effettivi tramite chat, ripristino dello stato, coda, interruzione e richieste di controllo. Un cambio di account non dovrebbe adottare automaticamente la sessione attiva dell'account precedente.

Invia un turno in streaming

POST /api/ai/chat/stream restituisce un corpo SSE. Richiede un messaggio non vuoto anche se lo schema di trasporto rende facoltativi diversi campi in modo che i gestori possano restituire messaggi di convalida leggibili. La seguente domanda generale disabilita l'inserimento del contesto memorizzato, rendendolo adatto per controllare il trasporto indipendentemente dalla geometria del progetto.

Una prima richiesta in streaming
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"

Aggiungere projectId e drawingId per lavorare su un disegno specifico. mode accetta generale, cad, cam, gcode o percorso utensile. UN model il selettore può contenere fornitore, modello, livello e variante; i livelli sono veloci, bilanciati o migliori. Scopri gli ID provider/modello dal catalogo in esecuzione anziché codificare in modo rigido un'ipotesi su quale fornitore sia disponibile.

permissionMode accetta approva, chiedi o automatico. I gruppi di strumenti distruttivi mantengono l'approvazione umana in ogni modalità. IL agent il campo seleziona un ruolo di assistente configurato; Leggere GET /api/ai/agents piuttosto che indovinare gli ID dei ruoli.

Leggere i frame di dati SSE completi

Il percorso utilizza la modalità dati SSE di Effect: ogni payload di dati SSE è un record di eventi JSON con a type discriminatore. Un blocco di rete non è un limite di eventi. I caratteri JSON, terminazioni di riga e UTF-8 possono essere suddivisi tra le letture. Accumula testo decodificato e analizza solo fotogrammi completi, unendo più linee di dati quando presenti.

Un lettore di frame di dati JSON minimo
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()
  }
}

Questo lettore dimostra l'inquadratura; un client di produzione deve inoltre convalidare ogni evento rispetto al contratto condiviso o utilizzare il client di streaming generato con tipizzazione. Ignora intenzionalmente i campi di commento/controllo SSE perché lo stato dell'applicazione di questo endpoint risiede nei record di dati JSON. Il rilascio di un flusso client è una pulizia del trasporto, non una richiesta esplicita di interrompere il lavoro lato server.

Visualizza i progressi senza duplicare i contenuti

Avvia il recupero con lo stesso portatore e corpo JSON mostrati sopra, quindi esegui l'iterazione del lettore. Aggiungere delta.text per visualizzare il testo della risposta e il percorso reasoning.text al proprio punto di vista. UN done l'evento porta il testo della risposta finale; sostituire o riconciliare la risposta accumulata con quel valore finale invece di aggiungere nuovamente l'intera risposta.

EventiStato da mantenere
turn, session_status, stepStato occupato/riprova e passaggio corrente; una fase di rifinitura può essere seguita da un'altra fase nello stesso turno.
tool_call, tool_progress, tool_resultUn record per callId con nome, input, avanzamento dell'argomento, output e successo; l'agente identifica uno specialista quando presente.
tool_diagnosticRisultati di argomento/esecuzione/parziale/verifica, gravità, testo di ripristino e mutazione facoltativa applicata.
todo, goalSostituisci l'istantanea del piano/obiettivo corrente anziché aggiungere copie ripetute.
retry, compaction, compactedSpiega il lavoro continuato preservando il testo corrente della conversazione e l'identità della sessione.
heartbeatNessun contenuto visibile; il server ne invia uno ogni cinque secondi circa mentre il flusso di origine rimane attivo.

Correlare le chiamate degli strumenti e i risultati in base a callId, non la loro posizione nell'array. Uno strumento non riuscito può restituire dati diagnostici utili mentre l'assistente continua. Un errore del provider dopo uno strumento di modifica riuscito non ripristina la modifica della geometria.

Rispondi a permessi e domande

A permission_request include sessionId, requestId, autorizzazione e modelli. Presentare la richiesta alla persona e inviare la decisione selezionata POST /api/ai/permission/reply. L'azione di controllo della sessione equivalente è reply_permission. Il server verifica che la richiesta sia ancora in sospeso nella conversazione o in una sessione figlio ammessa.

Corpo della risposta all'autorizzazione
{
  "sessionId": "inspection-demo",
  "requestId": "request-id-from-the-event",
  "decision": "once"
}

La decisione è una volta, sempre o rifiutata. Si tratta di una decisione relativa all'autorizzazione del server, distinta da un argomento di azione come la conferma. Una sospensione question_request contiene un array di domande ordinato. Rispondi tramite /api/ai/sessions/control con l'azione answer_question, l'ID della richiesta e un array di risposte per domanda; rifiutarlo con reject_question.

Rispondi a una domanda a scelta singola
{
  "sessionId": "inspection-demo",
  "action": "answer_question",
  "requestId": "question-id-from-the-event",
  "answers": [["The selected option label"]]
}

Ogni risposta deve essere non vuota. Una domanda a scelta singola rifiuta più risposte e custom=false consente solo le etichette elencate. Conserva le etichette esatte e l'ordine delle domande. Se il server dice che la richiesta non è più in sospeso, aggiorna i controlli invece di pubblicare ripetutamente la vecchia risposta.

Metti in coda, guida e interrompi esplicitamente il lavoro

Utilizzo POST /api/ai/chat/queue per un messaggio inviato mentre il turno funziona. Richiede sessionId, message, projectId opzionale e consegna. queue è l'impostazione predefinita e attende il passaggio sicuro successivo; steer chiede all'host di incorporarlo tempestivamente. UN user_message l'evento segnala che il messaggio è entrato nella conversazione.

Utilizzo POST /api/ai/chat/abort con l'identità della conversazione quando la persona preme Interrompi. Questo ferma il turno e si unisce ai lavoratori associati. Non implementare Stop esclusivamente annullando il fetch: perdere il flusso HTTP non è la stessa operazione. Una sessione può quindi accettare un altro turno senza creare una nuova identità di progetto.

POST /api/ai/sessions/remove rimuove la sessione host dopo aver annullato e unito i suoi figli. Restituisce un flag rimosso. La rimozione di una conversazione non è un rollback delle modifiche al progetto apportate dai suoi strumenti; rivederli o ripristinarli tramite le API del progetto/della cronologia quando necessario.

Recupera dopo aver perso lo streaming

Quando il lettore termina senza un evento finale, considera il completamento come sconosciuto. Chiamata POST /api/ai/sessions/state con lo stesso sessionId e projectId. La risposta contiene occupato e testo, con strumenti/cose da fare/eventi diagnostici, utilizzo e attività recuperati facoltativi. Ricostruisci l'interfaccia utente da questo snapshot; la riproduzione degli eventi dello strumento recuperati non li esegue nuovamente.

Quindi richiedi /api/ai/sessions/control con lo stato dell'azione per ispezionare autorizzazioni/domande in sospeso, attività del lavoratore, obiettivo e policy di contesto. Una svolta impegnativa in attesa di una domanda senza risposta ha bisogno di una risposta, non di una seconda copia della richiesta originale. Una sessione segnalata come inattiva con testo accumulato può essere visualizzata come output ripristinato anche se la connessione TCP originale è scomparsa.

Mantenere una distinzione tra errore di rete, evento di errore provider/configurazione digitato, interruzione esplicita e rimozione della sessione. Riprovare la richiesta del modello solo dopo aver verificato cosa ha effettivamente fatto il primo turno. In particolare, leggere il disegno corrente dopo una diagnosi di mutazione parziale o un intervento di editing interrotto.

All'interno del server, la connessione di un modello interrotto viene ripresa fino a cinque volte con un backoff di 2 secondi che raddoppia fino a 30 secondi. Un client che si riconnette al flusso dovrebbe comunque leggere lo stato della sessione, poiché tali curriculum possono completare il lavoro che il client non ha visto nel flusso.

Contesto, allegati e compattazione

includeContext: false disabilita l'acquisizione del contesto memorizzato; il testo contestuale esplicito può ancora essere fornito per quel turno. I normali requisiti permanenti degli ordini di assemblaggio, le osservazioni correnti, gli snapshot dell'interfaccia utente, la memoria e i riferimenti di supporto, li deduplica e applica un budget aggregato di 24,000 caratteri. Vengono riportati i blocchi omessi/troncati; questo budget non è la finestra dei token del fornitore.

Gli allegati includono nome, MIME facoltativo, dati e codifica testo/base64 facoltativa. Ce ne sono al massimo cinque per richiesta. Le immagini sono limitate a 5 MiB ciascuna, i PDF a 10 MiB e il testo a 2,000,000 caratteri; anche il testo breve fino a 6,000 caratteri appare in linea. Il testo conservato per gli strumenti ha l'ambito della memoria nell'ambito del progetto, pertanto la riconnessione è diversa dal riavvio del servizio. I file non supportati vengono rifiutati con motivazioni.

Il contesto dell'azione di controllo richiede una suddivisione dettagliata del token; il polling ordinario dello stato evita che il tokenizzatore funzioni. L'azione compatta chiede all'ospite di riassumere il materiale delle conversazioni più vecchie. Il consolidamento della memoria è un'operazione host separata. Mantieni le richieste di disegno e produzione di origine tramite le proprie API anziché fare affidamento su una conversazione compattata per riprodurre la geometria esatta.

Separare gli errori di trasporto dai risultati del dominio

HTTP 401 significa che la richiesta non ha superato l'autorizzazione della portante. Un 400 prima dello streaming indica comunemente un messaggio vuoto, un'identità di sessione inaccessibile, una combinazione progetto/disegno non valida o un corpo non valido. Una volta stabilito un flusso, il gestore può emettere un evento di errore con classificazione config, provider, interrotta o interna; gestire quell'evento anche quando lo stato della risposta era 200.

Un risultato dello strumento con ok=false è un risultato dello strumento, non necessariamente un flusso non riuscito. Mostra l'output e la diagnostica in modo che l'assistente o la persona possano riprendersi. Una coda di lavoro può anche essere piena: lo scheduler consente quattro attività attive e sedici in attesa. Un lettore interrotto può essere recuperato entro tentativi limitati; il lavoro di modifica viene considerato incerto anziché riprodotto automaticamente.

Leggi i contratti eseguibili packages/api/src/groups/ai.ts e ai-events.ts; ammissione e comportamento del filo in apps/sidecar/src/http/ai.ts; mappatura della sessione in packages/ai/src/service.ts; convalida della domanda in nikcli/session-controls.ts; sincronizzazione del battito cardiaco heartbeat.ts. Queste fonti definiscono quale risultato il cliente deve presentare, indipendentemente dalla prosa dell'assistente.

Riferimento all'evento

Ogni SSE data: frame è un oggetto JSON con a type e un sessionId. Analizza solo i frame completi; un frame può arrivare suddiviso in blocchi di rete.

tipoCampi chiaveCome gestire
turnphase: start, recovering, continuing, finish; opzionale reasonMostra lo stato di avanzamento. recovering significa che la connessione al modello viene ripresa.
session_statusstatus: busy, idle, retryAbilita o disabilita il pulsante di invio e la coda.
user_messageil messaggio utente memorizzatoRiconciliare l'interfaccia utente ottimistica.
deltatextAggiungi alla risposta visibile.
reasoningtesto argomentativoOpzionale; mostrare in un'area pieghevole.
step, todo, goalpianificare i progressiVisualizza l'elenco o il piano delle attività.
tool_callcallId, strumento, argomenti, facoltativo agentMostra quale strumento viene eseguito e per quale specialista.
tool_progresscallId, progressoAggiorna uno strumento di lunga durata.
tool_resultcallId, risultatoMostra il risultato; abbinalo callId.
tool_diagnosticdettagli inclusi mutationAppliedDecidi se il modello è cambiato. Leggilo prima di riprovare una modifica.
permission_request / permission_resolved / permission_blockedrequestId; rispondere once, always, rejectChiedi all'utente, quindi POST /api/ai/permission/reply.
question_request / question_resolveddati della domandaPresentare la domanda e inviare la risposta.
context_snapshot, compaction, compactedstato del contestoInformare l'utente che la cronologia precedente è stata riepilogata.
retryattempt, messageMostra un avviso temporaneo; non inviare nuovamente la richiesta.
donetext, facoltativo usage, facoltativo recoveredTesto finale del turno. Sostituisci i delta accumulati con esso.
errorcode: provider, config, aborted, internal; messageTermina il turno nell'interfaccia utente con il messaggio; config di solito significa credenziali mancanti.
heartbeatnessunoKeep-alive circa ogni 5 secondi; ignorare.

Un client di streaming completo

stream-turn.ts
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}`)
    }
  }
}

Se la connessione si interrompe, non inviare nuovamente il messaggio. Leggi la sessione con POST /api/ai/sessions/state, perché il server potrebbe aver ripreso la connessione del modello e terminato il turno senza di te. Utilizzo /api/ai/chat/abort fermare una svolta deliberatamente, e /api/ai/chat/queue per aggiungere un messaggio mentre la sessione è occupata.

Come appare uno streaming senza un provider

Senza alcun provider AI configurato, il flusso si apre comunque e risponde con un errore digitato, seguito da un battito cardiaco. Catturato da un sidecar fresco:

POST /api/ai/chat/stream
data: {"type":"error","sessionId":"","code":"config","message":"No AI provider is configured. Add an API key in the AI settings."}

data: {"type":"heartbeat"}
  • Lo stato HTTP è ancora 200 e il tipo di contenuto text/event-stream: i problemi di configurazione sono risultati del dominio, forniti come eventi.
  • sessionId è vuoto perché non è stata creata alcuna sessione.
  • heartbeat i frame mantengono viva la connessione; ignorali nel rendering e non contarli come progressi.
  • Ogni fotogramma termina con una riga vuota. Analizza dividendo su righe vuote e unendo il file data: linee di un frame, come fa l'iteratore SDK.

Metti alla prova il tuo cliente senza un modello

Indirizza un client verso un sidecar senza credenziali: esercita l'intero percorso SSE e il ramo di errore senza alcun costo.

Nel 3D

Archiviazione delle conversazioni Trascina per orbitare · scorri per ingrandire · trascina con il tasto destro per eseguire la panoramica
Conversazioni, Messaggi e relative tabelle con relative chiavi esterne, dalle migrazioni.