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:
{
"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 umfalsefabricado. Não é checagem de saldo — crédito esgotado ainda aparece como erro no turno.max_output_tokens— teto de saída documentado pelo provedor;nullquando o provedor não publica.supports_thinking— o runtime expõe o raciocínio deste modelo separado do texto final (thinkingnos eventos/respostas).thinking_efforts+default_thinking_effort— os valores dereasoning_effortque 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:
- Crie a ferramenta (
POST /v1/tools). O nome segue^http_[a-z0-9_]{1,120}$. - Crie o segredo com um nome do alfabeto do resolvedor (
PUT /v1/tool-secrets):NOMEcasa[A-Za-z0-9_-]{1,128}(letras, dígitos,_e-). Fora disso o upsert devolve400. (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.) - Anexe a ferramenta ao agente (
PATCH /v1/agents/{id}comtools= a união do que já estava + a nova). O modelo só vê o que está notools[]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. - Teste antes de depender (
POST /v1/tools/{id}/testcom 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:
- 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). - 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-IDexiste quando um agente chamou a ferramenta (o id vem da invocação do provider). O dispatch one-shot dePOST /v1/tools/{id}/testnã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) |