Pular para o conteúdo
CADCAMFUN
Docs

Documentação

Crie um cliente assistente de streaming

Abra um turno, renderize seus eventos reais, responda solicitações pendentes e recupere a conversa após uma queda de conexão.

Traduzido automaticamente do inglês. Ver o original em inglês

Conecte-se ao sidecar em execução

Um cliente de assistente de streaming se conecta ao mesmo sidecar local que o desktop. Obtenha o URL de loopback atribuído e o token de portador do aplicativo atual da conexão com a qual você está integrando. Uma porta salva no lançamento de ontem não é um endereço durável: o sidecar solicita uma porta efêmera cada vez que é iniciado. Um token de sessão de conta, um token de emparelhamento complementar e um token de ponte de ação com escopo têm finalidades diferentes e não podem substituir o portador do aplicativo nesta rota de chat.

Antes de enviar uma curva, leia GET /api/ai/status, /api/ai/providerse opcionalmente /api/ai/models?configuredOnly=true. Uma credencial configurada significa que uma chave está presente; isso não prova que o provedor aceitará a solicitação ou que um terminal oferece suporte ao modelo selecionado. O host padrão é o nikcli incorporado. O modo direto pode transmitir uma resposta de modelo, mas não fornece o loop de ferramentas persistente do host incorporado ou o agendador especializado.

Este guia usa CADCAMFUN_URL e CADCAMFUN_TOKEN como variáveis do shell do cliente. Forneça-os por meio do gerenciamento de conexão existente. Veja o Referência da API de IA para os esquemas exatos e Sidecar para comportamento de lançamento. Os exemplos não instalam credenciais nem escolhem um modelo em seu nome.

Escolha o projeto, o desenho e a identidade da sessão

O cliente pode fornecer um sessionId no primeiro turno e reutilizá-lo nos turnos posteriores. O serviço mapeia esse identificador de cliente para a sessão de host real e verifica a ligação antes de cada prompt. Retenha a identidade do evento/sessão como estado de conversa, em vez de criar um identificador diferente sempre que a IU se reconectar.

Um verdadeiro projectId dá ao turno acesso aos recursos permitidos daquele projeto. Um opcional drawingId deve pertencer a esse projeto; ele vincula as ferramentas do turno a esse desenho. Um desenho não pode ser nomeado sem o seu projeto. Uma sessão que já pertence a outra conta ou projeto é recusada. Estas são verificações de admissão antes que o modelo possa executar as ferramentas do projeto, e não dicas em um prompt do sistema.

Para uma conversa explicativa geral, omita projeto e desenho. O aplicativo usa seu contexto padrão. Para trabalho de geometria, carregue os IDs reais do projeto/desenho selecionados por meio de bate-papo, recuperação de estado, fila, cancelamento e solicitações de controle. Uma troca de conta não deve adotar silenciosamente a sessão ativa da conta anterior.

Envie um turno de streaming

POST /api/ai/chat/stream retorna um corpo SSE. Requer uma mensagem que não esteja em branco, embora o esquema de transporte torne vários campos opcionais para que os manipuladores possam retornar mensagens de validação legíveis. A questão geral a seguir desativa a injeção de contexto armazenado, tornando-a adequada para verificar o transporte independentemente da geometria do projeto.

Uma primeira solicitação transmitida
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"

Adicionar projectId e drawingId para trabalhar em um desenho específico. mode aceita geral, cad, cam, gcode ou percurso da ferramenta. UM model o seletor pode conter provedor, modelo, nível e variante; os níveis são rápidos, equilibrados ou melhores. Descubra IDs de provedor/modelo no catálogo em execução, em vez de codificar uma suposição sobre qual fornecedor está disponível.

permissionMode aceita aprovar, perguntar ou automático. Grupos de ferramentas destrutivas mantêm a aprovação humana em todos os modos. O agent campo seleciona uma função de assistente configurada; ler GET /api/ai/agents em vez de adivinhar os IDs das funções.

Leia frames de dados SSE completos

A rota usa o modo de dados SSE do Effect: cada carga útil de dados SSE é um registro de evento JSON com um type discriminador. Um pedaço de rede não é um limite de evento. JSON, finais de linha e caracteres UTF-8 podem ser divididos entre leituras. Acumule texto decodificado e analise apenas quadros completos, unindo múltiplas linhas de dados quando presentes.

Um leitor mínimo de quadro de dados JSON
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 leitor demonstra o enquadramento; um cliente de produção também deve validar cada evento em relação ao contrato compartilhado ou usar o cliente de streaming gerado digitado. Ele ignora intencionalmente os campos de comentário/controle SSE porque o estado do aplicativo desse endpoint reside em registros de dados JSON. Liberar um fluxo de cliente é uma limpeza de transporte, não uma solicitação explícita para abortar o trabalho no servidor.

Renderize o progresso sem duplicar o conteúdo

Inicie a busca com o mesmo portador e corpo JSON mostrados acima e, em seguida, itere o leitor. Acrescentar delta.text para texto de resposta visível e rota reasoning.text à sua própria visão. UM done o evento carrega o texto da resposta final; substitua ou reconcilie a resposta acumulada com o valor final em vez de anexar a resposta inteira novamente.

EventosEstado para manter
turn, session_status, stepEstado ocupado/nova tentativa e etapa atual; uma etapa de finalização pode ser seguida por outra etapa no mesmo turno.
tool_call, tool_progress, tool_resultUm registro por callId com nome, entrada, progresso do argumento, saída e sucesso; agente identifica um especialista quando presente.
tool_diagnosticDescobertas de argumento/execução/parcial/verificação, gravidade, texto de recuperação e mutação opcionalApplied.
todo, goalSubstitua o instantâneo atual do plano/meta em vez de anexar cópias repetidas.
retry, compaction, compactedExplique o trabalho contínuo preservando o texto atual da conversa e a identidade da sessão.
heartbeatNenhum conteúdo visível; o servidor envia um a cada cinco segundos enquanto o fluxo de origem permanece ativo.

Correlacionar chamadas de ferramenta e resultados por callId, não a posição da matriz. Uma ferramenta com falha pode retornar diagnósticos úteis enquanto o assistente continua. Um erro do provedor após uma ferramenta de edição bem-sucedida não reverte essa alteração de geometria.

Responder permissões e perguntas

A permission_request inclui sessionId, requestId, permissão e padrões. Apresentar a solicitação à pessoa e enviar a decisão selecionada para POST /api/ai/permission/reply. A ação de controle de sessão equivalente é reply_permission. O servidor verifica se a solicitação ainda está pendente na conversa ou em uma sessão filha admitida.

Corpo de resposta de permissão
{
  "sessionId": "inspection-demo",
  "requestId": "request-id-from-the-event",
  "decision": "once"
}

A decisão é uma vez, sempre ou rejeitar. É uma decisão de permissão do servidor, distinta de um argumento de ação como confirmar. Um pendente question_request contém uma matriz de perguntas ordenadas. Responder através /api/ai/sessions/control com ação answer_question, o ID da solicitação e uma matriz de resposta por pergunta; rejeitá-lo com reject_question.

Responda a uma pergunta de escolha única
{
  "sessionId": "inspection-demo",
  "action": "answer_question",
  "requestId": "question-id-from-the-event",
  "answers": [["The selected option label"]]
}

Toda resposta não deve ser vazia. Uma pergunta de escolha única rejeita múltiplas respostas e custom=false permite apenas rótulos listados. Preserve os rótulos exatos e a ordem das perguntas. Se o servidor disser que a solicitação não está mais pendente, atualize os controles em vez de postar repetidamente a resposta antiga.

Enfileirar, direcionar e interromper explicitamente o trabalho

Usar POST /api/ai/chat/queue para uma mensagem enviada enquanto o turno funciona. Leva sessionId, mensagem, projectId opcional e entrega. queue é o padrão e aguarda a próxima etapa segura; steer pede ao anfitrião para incorporá-lo imediatamente. UM user_message evento sinaliza que a mensagem entrou na conversa.

Usar POST /api/ai/chat/abort com a identidade da conversa quando a pessoa pressiona Parar. Isso interrompe o turno e se junta aos trabalhadores associados. Não implemente Stop apenas cancelando a busca: perder o fluxo HTTP não é a mesma operação. Uma sessão pode então aceitar outro turno sem criar uma nova identidade de projeto.

POST /api/ai/sessions/remove remove a sessão do host após cancelar e ingressar em seus filhos. Ele retorna um sinalizador removido. A remoção de uma conversa não é uma reversão das edições do projeto feitas por suas ferramentas; revise ou restaure-os por meio das APIs de projeto/histórico quando necessário.

Recuperar depois de perder o stream

Quando o leitor termina sem um evento final, trate a conclusão como desconhecida. Chamar POST /api/ai/sessions/state com o mesmo sessionId e projectId. A resposta contém ocupado e texto, com eventos, uso e tarefas opcionais de ferramentas/tarefas/diagnóstico recuperados. Reconstrua a UI a partir deste instantâneo; reproduzir eventos de ferramenta recuperados não os executa novamente.

Então solicite /api/ai/sessions/control com status de ação para inspecionar permissões/perguntas pendentes, tarefas de trabalho, meta e política de contexto. Uma curva ocupada aguardando uma pergunta sem resposta precisa de uma resposta, não de uma segunda cópia do prompt original. Uma sessão relatada como inativa com texto acumulado pode ser exibida como saída recuperada mesmo que a conexão TCP original tenha desaparecido.

Mantenha uma distinção entre falha de rede, evento de erro de provedor/configuração digitado, aborto explícito e remoção de sessão. Tente novamente a solicitação do modelo somente depois de verificar o que o primeiro turno realmente fez. Em particular, leia o desenho atual após um diagnóstico de mutação parcial ou trabalho de edição interrompido.

Dentro do servidor, uma conexão de modelo interrompida é retomada até cinco vezes com uma espera de 2 segundos que dobra para 30 segundos. Um cliente que se reconecta ao fluxo ainda deve ler o estado da sessão, porque essas retomadas podem concluir o trabalho que o cliente não viu no fluxo.

Contexto, anexos e compactação

includeContext: false desativa a aquisição de contexto armazenado; texto de contexto explícito ainda pode ser fornecido para esse turno. A montagem normal ordena requisitos permanentes, observações atuais, instantâneos de UI, memória e referências de suporte, desduplica-os e aplica um orçamento agregado de 24,000 caracteres. Blocos omitidos/truncados são relatados; este orçamento não é a janela de token do provedor.

Os anexos incluem nome, MIME opcional, dados e codificação opcional de texto/base64. São no máximo cinco por solicitação. As imagens são limitadas a 5 MiB cada, os PDFs a 10 MiB e o texto a 2,000,000 caracteres; textos curtos de até 6,000 caracteres também aparecem embutidos. O texto retido para ferramentas tem escopo de projeto na memória, portanto, reconectar é diferente de reiniciar o serviço. Arquivos não suportados são rejeitados com motivos.

O contexto da ação de controle solicita um detalhamento detalhado do token; a pesquisa de status comum evita o funcionamento do tokenizer. O Action Compact pede ao anfitrião para resumir o material da conversa mais antiga. A consolidação de memória é uma operação de host separada. Persista nas solicitações de desenho e fabricação de origem por meio de suas próprias APIs, em vez de depender de uma conversa compactada para reproduzir a geometria exata.

Separe os erros de transporte dos resultados do domínio

HTTP 401 significa que a solicitação não passou na autorização do portador. Um 400 antes do streaming geralmente significa uma mensagem vazia, identidade de sessão inacessível, combinação de projeto/desenho inválida ou corpo malformado. Depois que um fluxo é estabelecido, o manipulador pode emitir um evento de erro com configuração, provedor, abortado ou classificação interna; lidar com esse evento mesmo quando o status da resposta era 200.

Um resultado de ferramenta com ok=false é um resultado de ferramenta, não necessariamente um fluxo com falha. Mostre sua saída e diagnóstico para que o assistente ou pessoa possa se recuperar. Uma fila de trabalhadores também pode estar cheia: o agendador permite quatro tarefas ativas e dezesseis tarefas em espera. Uma leitora interrompida pode ser recuperada dentro de tentativas limitadas; o trabalho de edição é tratado como incerto, em vez de reproduzido automaticamente.

Leia os contratos executáveis em packages/api/src/groups/ai.ts e ai-events.ts; admissão e comportamento de fio em apps/sidecar/src/http/ai.ts; mapeamento de sessão em packages/ai/src/service.ts; validação de pergunta em nikcli/session-controls.ts; tempo de batimento cardíaco em heartbeat.ts. Essas fontes definem qual resultado o cliente deve apresentar, independentemente da prosa do assistente.

Referência de evento

Cada SSE data: frame é um objeto JSON com um type e um sessionId. Analise apenas quadros completos; um quadro pode chegar dividido em pedaços da rede.

tipoCampos-chaveComo lidar
turnphase: start, recovering, continuing, finish; opcional reasonMostrar estado de progresso. recovering significa que a conexão com o modelo está sendo retomada.
session_statusstatus: busy, idle, retryHabilite ou desabilite o botão enviar e a fila.
user_messagea mensagem do usuário armazenadaReconcilie a UI otimista.
deltatextAnexe à resposta visível.
reasoningtexto de raciocínioOpcional; mostrar em uma área dobrável.
step, todo, goalplanejar o progressoRenderize a lista de tarefas ou plano.
tool_callcallId, ferramenta, argumentos, opcional agentMostre qual ferramenta é executada e para qual especialista.
tool_progresscallId, progressoAtualize uma ferramenta de longa duração.
tool_resultcallId, resultadoMostre o resultado; combine com callId.
tool_diagnosticdetalhes incluindo mutationAppliedDecida se o modelo mudou. Leia-o antes de tentar novamente uma edição.
permission_request / permission_resolved / permission_blockedrequestId; responder once, always, rejectPergunte ao usuário, então POST /api/ai/permission/reply.
question_request / question_resolveddados da perguntaApresente a pergunta e envie a resposta.
context_snapshot, compaction, compactedestado de contextoInforme ao usuário que o histórico mais antigo foi resumido.
retryattempt, messageMostrar um aviso transitório; não reenvie o prompt.
donetext, opcional usage, opcional recoveredTexto final da virada. Substitua os deltas acumulados por ele.
errorcode: provider, config, aborted, internal; messageEncerre o turno na UI com a mensagem; config geralmente significa falta de credenciais.
heartbeatnenhumMantenha-se ativo a cada 5 segundos; ignorar.

Um cliente de 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 a conexão cair, não envie a mensagem novamente. Leia a sessão com POST /api/ai/sessions/state, porque o servidor pode ter retomado a conexão do modelo e finalizado o turno sem você. Usar /api/ai/chat/abort parar uma curva deliberadamente, e /api/ai/chat/queue para adicionar uma mensagem enquanto a sessão está ocupada.

Qual é a aparência de um stream sem um provedor

Sem nenhum provedor de IA configurado, o stream ainda abre e responde com um erro de digitação, seguido por uma pulsação. Capturado de um sidecar novo:

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"}
  • O status HTTP ainda é 200 e o tipo de conteúdo text/event-stream: problemas de configuração são resultados de domínio, entregues como eventos.
  • sessionId está vazio porque nenhuma sessão foi criada.
  • heartbeat os frames mantêm a conexão ativa; ignore-os na renderização e não os conte como progresso.
  • Cada quadro termina com uma linha em branco. Analise dividindo em linhas em branco e juntando o data: linhas de um quadro, como faz o iterador do SDK.

Teste seu cliente sem modelo

Aponte um cliente para um sidecar sem credenciais: ele exercita o caminho SSE completo e a ramificação de erro sem nenhum custo.

Em 3D

Armazenamento de conversas Arraste para orbitar · role para ampliar · arraste com o botão direito para deslocar
Conversação, Mensagem e tabelas relacionadas com suas chaves estrangeiras, das migrações.