Ferramentas e skills

É um agente de IA? Busque a skill de onboarding e leia toda a API em texto limpo — skill · llms.txt · llms-full.txt

Ferramentas e skills#

GETCatálogo de ferramentas#

GET /v1/agent-tools — as ferramentas selecionáveis (embutidas ∪ custom): { tools: [ { name, label, description } ] }.

GETCatálogo de modelos e provedores#

GET /v1/llm-catalog (alias: GET /v1/providers) — o catálogo de modelos, agrupado por provedor. Use-o para popular a sua UI com provedores, modelos, janelas de contexto e efforts REAIS em vez de descobrir por tentativa:

json
{
  "providers": [
    {
      "provider": "kimi", "label": "Kimi", "available": true,
      "models": [
        { "id": "k3", "provider": "kimi", "label": "K3",
          "context_window": 1048576, "max_output_tokens": null,
          "input_cost_per_1m": 0, "output_cost_per_1m": 0,
          "supports_tools": true, "supports_vision": true,
          "supports_thinking": true,
          "thinking_efforts": ["low", "high", "max"],
          "default_thinking_effort": "high" }
      ]
    }
  ]
}

Semântica dos campos novos (contrato: desconhecido = null, nunca um chute):

  • available — se o runtime tem credencial configurada para o provedor AGORA. null = indeterminado (não conseguimos consultar o engine no momento); nunca um false fabricado. Não é checagem de saldo — crédito esgotado ainda aparece como erro no turno.
  • max_output_tokens — teto de saída documentado pelo provedor; null quando o provedor não publica.
  • supports_thinking — o runtime expõe o raciocínio deste modelo separado do texto final (thinking nos eventos/respostas).
  • thinking_efforts + default_thinking_effort — os valores de reasoning_effort que efetivamente mudam o comportamento deste modelo, e o aplicado quando o agente não define nenhum. null = o modelo raciocina mas não há botão de effort plugado.

Entre os provedores estão Kimi (k3 com 1M de contexto, k3-256k, kimi-for-coding, kimi-for-coding-highspeed — todos thinking-only, efforts low|high|max) e MiniMax (MiniMax-M3, MiniMax-M2.7, MiniMax-M2.5, MiniMax-M2.1, MiniMax-M2 + variantes -highspeed). Em todos os provedores o content das respostas vem saneado: o raciocínio nunca chega inline no texto (nada de <think> cru) — ele vem no campo/evento thinking.

POSTCriar ferramenta REST#

POST /v1/tools (owner/admin) — corpo customToolRequest:

Campo Tipo Regra
name string obrigatório, regex ^http_[a-z0-9_]{1,120}$
url string obrigatório, validado contra SSRF
method string GET/POST/… (upper-case)
display_name, description string
header_template, query_template objeto mapa string→string; use ${secret:NOME} para segredos do cofre
body_template string
input_schema JSON schema dos argumentos
timeout_ms int default 15000
max_response_kb int default 256
enabled bool? default true
tenant_external_id string? escopa a ferramenta a UM cliente final; ausente = visível a toda a conta

Resposta 201: customToolResponse (ecoa tenant_id). Leituras (GET /v1/tools, /v1/tools/{id}) são all-auth. Erros: TOOL_NAME_INVALID, TOOL_URL_INVALID, SECRET_NOT_FOUND.

Checklist: do zero à ferramenta funcionando no turno. Criar a ferramenta NÃO basta — para o modelo enxergá-la e o segredo resolver, são quatro passos:

  1. Crie a ferramenta (POST /v1/tools). O nome segue ^http_[a-z0-9_]{1,120}$.
  2. Crie o segredo com um nome do alfabeto do resolvedor (PUT /v1/tool-secrets): NOME casa [A-Za-z0-9_-]{1,128} (letras, dígitos, _ e -). Fora disso o upsert devolve 400. (Antes de 2026-07-25 o cofre aceitava qualquer nome e um nome fora do alfabeto fazia o placeholder ${secret:...} viajar LITERAL na requisição: o endpoint respondia 401 como se a credencial estivesse errada, e nada indicava que o problema era a RESOLUÇÃO na origem, não o destino.)
  3. Anexe a ferramenta ao agente (PATCH /v1/agents/{id} com tools = a união do que já estava + a nova). O modelo só vê o que está no tools[] do agente: criar a ferramenta NÃO a anexa, e um agente criado ANTES dela não a recebe automaticamente — sem o patch, ele nunca a terá no catálogo do turno.
  4. Teste antes de depender (POST /v1/tools/{id}/test com args de exemplo): executa a chamada real (guardada contra SSRF) e mostra o que o endpoint recebeu. Um segredo que não resolve aparece aqui como 401 do destino — em segundos, não no meio de um run de minutos.

Escopo por tenant (opcional). POST /v1/tools e PUT /v1/tool-secrets ({ name, value, tenant_external_id? }) aceitam o eixo de tenant. Sem ele o recurso é da conta inteira — o comportamento de sempre. Com ele valem duas garantias no runtime:

  1. Um agente do tenant A monta apenas ferramentas do tenant A ou da conta inteira. A ferramenta de outro tenant é ignorada mesmo se o tools:[...] do agente citar o nome (o vínculo é por nome, então sem isso o nome seria a permissão).
  2. Um ${secret:NOME} dentro de uma ferramenta do tenant A resolve apenas segredos do tenant A ou da conta inteira. O segredo de outro tenant segue o mesmo caminho de um segredo inexistente (substituição vazia), então o valor nunca chega ao endpoint.

O nome continua único por conta — tenant é eixo de visibilidade, não namespace (o modelo escolhe a ferramenta pelo nome dentro do turno, então dois nomes iguais no mesmo catálogo seriam ambíguos). Use o seu próprio prefixo para segregar. O escopo é imutável: um PUT /v1/tool-secrets que mudaria o tenant de um segredo existente devolve 409 SECRET_SCOPE_IMMUTABLE — remova e recrie, para que uma rotação distraída nunca alargue silenciosamente um segredo de tenant para a conta toda.

Gerenciar ferramentas#

Rota Ação
GET /v1/tools · GET /v1/tools/{id} Listar / detalhar (all-auth)
PATCH /v1/tools/{id} Update esparso (owner/admin)
DELETE /v1/tools/{id} Remover (owner/admin)
POST /v1/tools/{id}/test Dry-run da ferramenta salva, com args de exemplo
POST /v1/tools/draft Tool-builder com IA: descreva a integração em texto, receba um rascunho de ferramenta
POST /v1/tools/test-draft Dry-run de um rascunho sem salvar (corpo = a ferramenta)
GET /v1/tools/{id}/versions · POST /v1/tools/{id}/versions/{vid}/restore Histórico de snapshots + restauração

Segredos do cofre (os ${secret:NOME} dos templates): GET /v1/tool-secrets lista (name + last4, nunca o valor), PUT /v1/tool-secrets cria/atualiza ({ name, value, tenant_external_id? }), DELETE /v1/tool-secrets/{name} remove.

Regras do ${secret:NOME}: o NOME casa [A-Za-z0-9_-]{1,128} — fora disso o PUT devolve 400 (a escrita e a leitura usam o mesmo alfabeto; um nome que o resolvedor não enxerga falha na hora de salvar, nunca no meio de um dispatch). A resolução é fail-closed: segredo ausente, indecifrável ou de outro tenant resolve para string vazia — nunca o placeholder literal — então o endpoint recebe uma credencial vazia. Se o seu endpoint responde 401 no dispatch, verifique nesta ordem: (a) o NOME no template bate exatamente com o nome no cofre; (b) o segredo está no mesmo escopo de tenant da ferramenta (ou é da conta inteira); (c) o last4 em GET /v1/tool-secrets é o valor que você espera.

Cabeçalhos confiáveis que a plataforma envia à sua ferramenta#

Cada dispatch carrega o escopo do run em cabeçalhos que a engine assina do lado dela — eles vêm do run request, nunca dos argumentos que o modelo escreveu, então sua ferramenta pode confiar neles para autorizar e para deduplicar:

Header Sempre? Significado
X-Tenant-ID sim conta (organização) dona do run
X-Project-ID sim projeto do run
X-Agent-ID sim agente que chamou a ferramenta
X-Session-ID sim conversa — estável entre turnos
X-Stream-ID sim ESTE run — muda a cada turno
X-Tool-Call-ID em run de agente ESTA invocação da ferramenta, dentro do turno
X-Project-Tenant-ID quando há cliente final do projeto (eixo de tenant)
X-Runtime-Token-ID quando há token prt_ que originou o run
X-End-User-ID / X-End-User-External-ID quando há usuário final vinculado ao run

X-Tool-Call-ID existe quando um agente chamou a ferramenta (o id vem da invocação do provider). O dispatch one-shot de POST /v1/tools/{id}/test não tem invocação de provider nenhuma, então esse header não vem no dry-run — trate-o como presente-em-run, ausente-em-teste, nunca como obrigatório.

Para idempotência use X-Tool-Call-ID, não o corpo. Ele identifica uma invocação única e é reenviado idêntico se o mesmo dispatch for repetido, enquanto X-Session-ID cobre a conversa inteira e X-Stream-ID o turno inteiro (vários chamados de ferramenta compartilham ambos). Qualquer id vindo no corpo é escrito pelo modelo e pode repetir.

timeout_ms é o prazo que vale. O deadline da sua ferramenta é o timeout_ms que você configurou (default 15000), limitado apenas pelo tempo restante do run — 600s no perfil padrão, 1800s em run_class:"coder". Não existe mais um teto de 60s do lado do cliente HTTP: uma ferramenta que declara 180000 recebe de fato os 180s, desde que o run ainda tenha essa folga.

GETSkills#

GET /v1/skills{ skills: [ { id, name, description, body, scope, read_only, … } ], total }. POST /v1/skills cria ({ name, description?, body }; SKILL_NAME_REQUIRED, SKILL_BODY_REQUIRED). Skills globais da plataforma são read-only (SKILL_READONLY).

Uma skill é um pacote de instruções reutilizável (nome + descrição + corpo). No prompt, o agente vê um catálogo progressivo: só nome+descrição entram de cara; quando a tarefa casa com a descrição, ele chama a tool load_skill(name) e recebe o corpo inteiro sob demanda.

Gerenciar skills#

Rota Ação
PATCH /v1/skills/{id} Update esparso (name/description/body)
DELETE /v1/skills/{id} Remover → 204
POST /v1/skills/{id}/preview Corpo { system_prompt? }{ header, body, injected, assembled }: o que entra no prompt de cara (header) e o que load_skill devolve (body). Determinístico, sem LLM
GET /v1/skills/{id}/versions · POST /v1/skills/{id}/versions/{vid}/restore Histórico + restauração

Skills de um agente (plug/unplug)#

Rota Ação
GET /v1/agents/{id}/skills { bindings: [ { skill_id, name, description, scope, read_only, enabled, priority } ], total }
PUT /v1/agents/{id}/skills Corpo { bindings: [ { skill_id, enabled?, priority? } ] } — define atomicamente o conjunto plugado (plug/unplug/reordenar/toggle)
PATCH /v1/agents/{id}/skills/{skillId} Corpo { enabled?, priority? } — liga/desliga ou reordena UMA skill plugada (404 SKILL_NOT_ACTIVE se não está plugada)