Gateway HTTP OpenAI-compatible (/v1/chat/completions, /v1/models) que expõe
o Antigravity CLI (agy) como se fosse um backend de LLM
qualquer — dá pra apontar qualquer client/SDK que fale API da OpenAI (curl, código,
extensões de editor, etc.) direto pro agy rodando localmente.
Projeto irmão: agy-gateway — mesma ideia, só que como bridge de Telegram em vez de API HTTP.
Cada request HTTP dispara um processo agy --print (modo sem TUI), captura a saída
e devolve no formato chat.completion da OpenAI. Por trás disso:
- Execuções serializadas — o
agytrava um banco local por processo; o gateway usa um semáforo (AGY_MAX_CONCURRENT, default 1) pra nunca rodar dois ao mesmo tempo. - Conversa isolada por cliente — manda o campo
user(padrão OpenAI) e o gateway mantém sua conversa separada das dos outros clientes. - Streaming de verdade —
stream: truedevolve Server-Sent Events incrementais, não só o texto todo de uma vez. - Aprovação de comandos — se o
agypedir permissão pra rodar algo no meio do caminho, o gateway não trava: devolve umapproval_ide o cliente aprova/nega via outro endpoint. - Auth opcional —
AGY_API_KEYliga checagem deAuthorization: Bearer.
Detalhes de arquitetura e invariantes internas estão em CLAUDE.md.
git clone git@github.com:ataliba/agy-gateway-ng.git
cd agy-gateway-ng
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
cp .env.example .envPré-requisito: agy (Antigravity CLI) instalado e autenticado na máquina
(agy rodado ao menos uma vez pra validar login).
| Var | Default | Uso |
|---|---|---|
AGY_BIN |
agy |
binário/caminho do CLI |
AGY_CONTINUE |
true |
continua a última conversa quando não há user no request |
MODELS_FILE |
models.yaml |
registro de aliases de modelo |
AGY_TIMEOUT |
300 |
timeout de processo travado e de espera por aprovação |
BRAIN_DIR |
~/.gemini/antigravity-cli/brain |
pasta onde o agy guarda conversas |
AGY_MAX_CONCURRENT |
1 |
quantos agy podem rodar ao mesmo tempo |
AGY_API_KEY |
(vazio) | se setado, exige Bearer token em /v1/* |
Modelos expostos ficam em models.yaml, mapeando um alias (agy-claude-sonnet-4-6)
pro nome real que o --model do agy espera.
.venv/bin/uvicorn main:app --reload --port 8000Ou em produção via systemd (unit já pronta em agy-gateway.service):
mkdir -p ~/.config/systemd/user
cp agy-gateway.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now agy-gatewayA imagem já vem com o Antigravity CLI instalado (instalador oficial, dentro do
Dockerfile). Login e conversas ficam num volume próprio (gemini_data),
isolado do host — sobrevive a restart/rebuild, só some com
docker compose down -v.
cp .env.example .env
docker compose up -d --buildPra usar a imagem já publicada no Docker Hub (cybernetus/agy-gateway-ng), sem
build local:
cp .env.example .env
docker compose -f docker-compose.prod.yml up -ddocker-compose.prod.yml não usa env_file — as vars vêm de ${VAR} com
default embutido. É de propósito: deploy via Portainer com agent não envia
o .env junto do stack, só o YAML, então env_file: .env falha calado. No
Portainer, preencha as vars em "Environment variables" na tela do stack; via
docker compose na CLI, exporte-as no shell ou use --env-file .env.
Na primeira vez, entre no container e faça o login do Google (o agy mostra uma
URL pra abrir num navegador):
docker compose exec agy-gateway agyDepois disso o gateway já usa essa sessão autenticada normalmente — não precisa
logar de novo a não ser que o volume gemini_data seja apagado.
BRAIN_DIR do .env é sobrescrito pelo compose pro caminho de dentro do
container (/root/.gemini/antigravity-cli/brain) — não precisa mexer nessa
variável pra rodar via Docker.
Instalador standalone (não depende do framework community-scripts) que cria um
LXC Debian 13 e instala tudo nativo (venv + systemd, sem Docker, em
/opt/api-gateway-ng). Roda no shell do host Proxmox:
bash -c "$(curl -fsSL https://raw.githubusercontent.com/ataliba/agy-gateway-ng/main/ct/agy-gateway-ng.sh)"Customiza via env var antes do comando — mesma convenção do
community-scripts/ProxmoxVE (var_cpu, var_ram, var_disk, var_version,
var_unprivileged), mais CTID, HOSTNAME, STORAGE, NET_CONFIG,
GATEWAY_PORT (ver cabeçalho de ct/agy-gateway-ng.sh):
var_cpu=2 var_ram=1024 var_version=13 bash -c "$(curl -fsSL https://raw.githubusercontent.com/ataliba/agy-gateway-ng/main/ct/agy-gateway-ng.sh)"No fim, falta só logar o agy dentro do container:
pct exec <CTID> -- agy.venv/bin/pip install -r requirements-dev.txt
.venv/bin/python -m pytest -qcurl http://127.0.0.1:8000/v1/modelscurl -X POST http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "agy-claude-sonnet-4-6",
"messages": [{"role": "user", "content": "oi"}],
"user": "ataliba"
}'user é o que faz o gateway lembrar da sua conversa entre chamadas — sem ele, cada
request sem contexto cai no --continue (segue a última conversa global do agy).
curl -N -X POST http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "agy-claude-sonnet-4-6",
"messages": [{"role": "user", "content": "oi"}],
"stream": true,
"user": "ataliba"
}'Uma resposta pode vir com HTTP 202:
{
"status": "permission_required",
"approval_id": "45e08c8581d34915abf6151021c35de4",
"command": "Run this command? [y/N]"
}Aprovar ou negar:
curl -X POST http://127.0.0.1:8000/v1/approvals/45e08c8581d34915abf6151021c35de4 \
-H "Content-Type: application/json" \
-d '{"approved": true}'Em modo streaming, o mesmo aviso chega como um chunk SSE extra
("permission_required": {...}) e o stream original continua depois que a
aprovação chegar por essa mesma rota.
curl http://127.0.0.1:8000/v1/models -H "Authorization: Bearer <sua-chave>"