# Copilot

> client.copilot controla as threads e as rodadas em streaming do Copilot. Só funciona dentro de um app do Nodaro, com a sessão do próprio usuário conectado.

Source: https://nodaro.ai/pt-BR/docs/developers/sdk/copilot

**`client.copilot`** controla o Copilot, o assistente de workflows do Nodaro. Uma **thread** é uma conversa aberta em um workflow, e uma **rodada** é uma mensagem mais tudo o que o assistente faz para respondê-la. Os métodos chamam `/v1/copilot/*`. Veja [Workflow Copilot](https://nodaro.ai/docs/get-started/workflow-copilot) para conhecer o recurso.

**O Copilot só funciona dentro de um app do Nodaro.** Todas as rotas recusam, com `403 in_app_only`, quem chama sem a sessão do próprio usuário conectado. O SDK lança esse erro como `ForbiddenError`, cujo `code` é `forbidden`, então verifique o `message` dele. Tokens de API e tokens OAuth não conseguem controlar o Copilot de ninguém. Use-o a partir de um app de navegador com [`supabaseAuth`](https://nodaro.ai/docs/developers/sdk/auth#supabaseauthsupabase). Ele funciona no Nodaro Cloud. Uma implantação com o recurso desativado responde `503 feature_disabled` quando você abre uma thread ou envia uma rodada.

## Métodos
| Método | O que faz |
| --- | --- |
| [`create(input)`](#createinput) | Abre uma thread em um workflow, ou em um novo |
| [`list(params)`](#listparams) | Lê a thread ativa de um workflow |
| [`get(id, opts?)`](#getid-opts) | Lê uma thread com as mensagens dela |
| [`archive(id)`](#archiveid) | Arquiva uma thread |
| [`cancel(id)`](#cancelid) | Interrompe a rodada em andamento |
| [`stream(threadId, opts)`](#streamthreadid-opts) | Envia uma mensagem e lê os frames da rodada à medida que chegam |

Estes métodos retornam o envelope `{ data }` da API como ele chega, exceto `stream()`, que emite frames.

## client.copilot
### create(input)
Abre uma thread. Informe um `workflowId` existente, ou um `prompt`, e o servidor cria um workflow a partir dele. Abrir um workflow que já tem uma thread ativa retorna essa thread em vez de uma segunda.

```ts
create(input: { workflowId?: string; prompt?: string; name?: string }): Promise<{
data: { thread: CopilotThread; workflow: CopilotThreadWorkflow }
}>
```

<TypeTable
type={{
workflowId: { type: 'string', description: "Um workflow existente. Informe workflowId ou prompt." },
prompt: { type: 'string', description: "Um prompt para iniciar um novo workflow." },
name: { type: 'string', description: "O nome do novo workflow, junto com prompt." },
}}
/>

```ts
const { data } = await client.copilot.create({ workflowId })
const threadId = data.thread.id
```

Uma thread tem `id`, `workflowId`, `runMode` (`ask`, que propõe execuções e espera, ou `auto`, que executa dentro de `autoRunLimitCredits`), `modelTier`, `allowPublishing`, `userTurnCount`, `lastMessageAt` e `createdAt`. `surface` indica o assistente a que a thread pertence; quando esse campo não vem, a implantação não informou.

### list(params)
Lê a thread ativa de um workflow, ou `null` quando não há nenhuma.

```ts
list(params: { workflowId: string }): Promise<{ data: { thread: CopilotThread | null } }>
```

<TypeTable
type={{
workflowId: { type: 'string', required: true, description: "O ID do workflow." },
}}
/>

```ts
const { data: { thread } } = await client.copilot.list({ workflowId })
```

### get(id, opts?)
Lê uma thread com as mensagens dela. A thread também traz um `status` derivado (`running` ou `idle`) e `activeTurnId`.

```ts
get(id: string, opts?: { after?: number; limit?: number }): Promise<{
data: { thread: CopilotThread; messages: CopilotMessage[] }
}>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "O ID da thread." },
after: { type: 'number', description: "Retorna apenas as mensagens posteriores a este número de sequência, para você se atualizar." },
limit: { type: 'number', description: "O tamanho da página, até o limite do servidor." },
}}
/>

```ts
const { data: { messages } } = await client.copilot.get(threadId, { after: lastSeq })
```

Cada mensagem tem `id`, `seq`, `turnId`, `role`, `createdAt` e `parts`: partes de texto e partes de chamada de ferramenta.

### archive(id)
Arquiva uma thread. As mensagens continuam legíveis; nada é excluído. Uma thread com uma rodada em andamento não pode ser arquivada.

```ts
archive(id: string): Promise<{ data: { archived: true } }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "O ID da thread." },
}}
/>

```ts
await client.copilot.archive(threadId)
```

### cancel(id)
Interrompe a rodada em andamento de uma thread e responde qual rodada pediu para interromper. O stream dessa rodada termina com um frame `done`.

```ts
cancel(id: string): Promise<{ data: { cancelling: true; turnId: string } }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "O ID da thread." },
}}
/>

```ts
await client.copilot.cancel(threadId)
```

### stream(threadId, opts)
Envia uma mensagem e emite os frames da rodada à medida que chegam, como server-sent events. Renderize o texto, a atividade das ferramentas e as propostas ao vivo, em vez de esperar a resposta completa. É o único destes métodos que gasta créditos.

```ts
stream(threadId: string, opts: {
message: string
baseVersion?: number
tier?: "economy" | "standard" | "premium"
signal?: AbortSignal
}): AsyncGenerator<CopilotStreamFrame>
```

<TypeTable
type={{
threadId: { type: 'string', required: true, description: "O ID da thread." },
message: { type: 'string', required: true, description: "A mensagem do usuário." },
baseVersion: { type: 'number', description: "A versão do workflow que o usuário vê." },
tier: { type: '"economy" | "standard" | "premium"', description: "O nível de modelo desta rodada." },
signal: { type: 'AbortSignal', description: "Encerra o stream da rodada." },
}}
/>

```ts
try {
for await (const frame of client.copilot.stream(threadId, { message: "Tidy the graph" })) {
if (frame.type === "token") appendText(frame.data.text)
if (frame.type === "run_proposed") await askTheUser(frame.data)
if (frame.type === "done") break
}
} catch (err) {
if ((err as Error).name !== "AbortError") throw err // a stopped turn is a normal ending
}
```

`CopilotStreamFrame` é uma união discriminada por `type`:

| Frame | O que traz |
| --- | --- |
| `metadata` | Os IDs da rodada, o modelo, `runMode`, `autoRunLimitCredits` e as configurações |
| `token` | Um trecho do texto da resposta |
| `tool_call` | Uma ferramenta que o assistente usa: `started`, depois `finished` ou `failed` |
| `workflow_updated` | Os nós adicionados, alterados e removidos, e a nova versão |
| `workflow_created` | Um workflow que o assistente criou |
| `run_proposed` | O que a rodada quer executar, para uma pessoa confirmar |
| `memory_saved` | Algo que o assistente decidiu lembrar |
| `usage` | Os tokens usados e `creditsCharged` |
| `done` | O fim: `completed`, `capped` ou `cancelled` |
| `error` | A rodada falhou, com um `code` e uma `message` |

O `data` de um frame é repassado como está, então os campos que este SDK não modela também chegam até você. Um tipo de frame que o SDK não modela é ignorado em vez de lançar um erro, então um servidor mais novo não consegue quebrar um cliente mais antigo. Esta versão não modela `action_proposed`, o cartão de proposta das threads na superfície `studio`, então essas propostas não chegam a quem faz a chamada.

**A duração da rodada fica por sua conta.** O `timeoutMs` do cliente não se aplica a uma rodada, porque uma rodada pode durar minutos. Passe `signal`, ou pare de iterar, o que encerra a requisição. Abortar rejeita a iteração com o `AbortError` do próprio runtime, não com um `NodaroError`, seja antes do primeiro frame, seja entre dois frames. Um status de erro na requisição inicial ainda lança o [erro tipado](https://nodaro.ai/docs/developers/sdk/errors) de sempre, antes de qualquer frame.

## Frequently asked questions

### Posso usar client.copilot com um token de API?

Não. Todas as rotas do Copilot recusam tokens de API e tokens OAuth com 403 in_app_only. Só a sessão do próprio usuário conectado, enviada com supabaseAuth a partir de um app do Nodaro, pode usá-lo.

### O que client.copilot.stream retorna?

Um iterador assíncrono de frames tipados: os metadados da rodada, trechos de texto, chamadas de ferramentas, atualizações do workflow, propostas de execução, o uso e um frame final done ou error. Percorra-o com for await.

### O tempo limite do cliente interrompe uma rodada longa do Copilot?

Não. O tempo limite não se aplica a uma rodada, porque uma rodada pode durar minutos. Passe um AbortSignal, ou pare de iterar, para encerrá-la.

### Quais frames podem não chegar a quem faz a chamada?

Os tipos de frame que esta versão do SDK não modela são ignorados. Isso inclui o cartão de proposta das threads na superfície studio, chamado action_proposed, então essas propostas não chegam a quem faz a chamada.
