Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ status:
docker-build:
docker build -t litellmrtksync:latest -t ghcr.io/pathbit/litellmrtksync:latest .

# 9093 e a porta de host deste painel (9091 e do 9RTKSync, 9092 do OminiRTkSync).
# 9093 e a porta de host deste painel (9091 e do 9RTKSync, 9092 do OmniRTkSync).
# Publicar na 9092 ocupava a porta do irmao e mostrava este painel no endereco
# que a documentacao atribui ao outro produto. Comentario FORA da receita: linha
# iniciada por # dentro de um alvo vai para o shell e aparece na saida.
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ still trusts but the provider has revoked, and rate-limit ceilings that
contradict each other — three states nobody is told about until a request fails.

Third of the RTKSync family, after [9RTKSync](https://github.com/pathbit/9RTKSync)
(9Router) and [OminiRTkSync](https://github.com/pathbit/OminiRTkSync) (OmniRoute).
(9Router) and [OmniRTkSync](https://github.com/pathbit/OmniRTkSync) (OmniRoute).

## Documentation

Expand All @@ -29,7 +29,7 @@ request; a push to `master` republishes the wiki automatically.

## Why this is not a copy of its siblings

9RTKSync and OminiRTkSync **renew** OAuth credentials: their gateway stores
9RTKSync and OmniRTkSync **renew** OAuth credentials: their gateway stores
tokens that expire, and nobody notices when one dies until a request fails.

LiteLLM has no consumer OAuth. Its credentials are provider API keys in the
Expand Down Expand Up @@ -145,7 +145,7 @@ side. The same goes for the gateways: each one has its own.
| OmniRoute | `20128` | `8082` |
| LiteLLM | `4000` | `8083` |
| 9RTKSync (dashboard) | `9090` | `9091` |
| OminiRTkSync (dashboard) | `9090` | `9092` |
| OmniRTkSync (dashboard) | `9090` | `9092` |
| LiteLlmRTKSync (dashboard) | `9090` | `9093` |

The article stack (`claudegravity`) keeps **`20128`**, the default 9Router port.
Expand Down
10 changes: 5 additions & 5 deletions docker-compose.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: litellmrtksync-stack

# Stack de exemplo: o proxy LiteLLM, o Postgres que ele exige, o 9Router para
# onde o proxy encadeia e o sincronizador. Mesma forma dos irmaos 9RTKSync e
# OminiRTkSync -- gateway, banco do gateway e o sincronizador ao lado, com o
# OmniRTkSync -- gateway, banco do gateway e o sincronizador ao lado, com o
# painel preso ao loopback.
#
# POR QUE ESTA STACK TEM O PROPRIO 9ROUTER
Expand Down Expand Up @@ -111,7 +111,7 @@ services:
# a propria, de gestao, onde ficam o Postgres, o 9Router desta stack e o
# sincronizador -- e e por ela que o encadeamento acontece hoje, pelo
# nome `litellmrtk-9router`; e a de inferencia, que continua existindo
# so para alcancar gateway de OUTRA stack (`ominirtk-router`) e agora e
# so para alcancar gateway de OUTRA stack (`omnirtk-router`) e agora e
# opcional para o caminho principal.
- litellmrtksync-net
- rtk-inference-net
Expand Down Expand Up @@ -167,7 +167,7 @@ services:
restart: unless-stopped
ports:
# Porta interna 9090, igual nos tres sincronizadores; publicada em 9093 no
# host (9091 do 9RTKSync, 9092 do OminiRTkSync), para que os tres paineis
# host (9091 do 9RTKSync, 9092 do OmniRTkSync), para que os tres paineis
# rodem lado a lado. O bind em 127.0.0.1 mantem o painel fora da rede.
- "127.0.0.1:9093:9090"
volumes:
Expand Down Expand Up @@ -330,9 +330,9 @@ networks:
#
# A de baixo e de INFERENCIA e e compartilhada de proposito. Ela responde ao
# sintoma "nao consigo conectar o LiteLLM com um gateway de OUTRA stack":
# sem ela, `getent hosts ominirtk-router` de dentro deste container nao
# sem ela, `getent hosts omnirtk-router` de dentro deste container nao
# resolve nada, porque cada stack vive so na sua rede. Com ela, um deployment
# pode apontar para http://ominirtk-router:20128/v1 ou http://9rtk-router:20128/v1.
# pode apontar para http://omnirtk-router:20128/v1 ou http://9rtk-router:20128/v1.
# Atencao: 127.0.0.1:8082 NAO serve de api_base -- dentro deste container o
# loopback e o proprio LiteLLM. Tem de ser o nome do servico.
#
Expand Down
34 changes: 17 additions & 17 deletions docs/wiki/Chaining-Gateways.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ hostname, like `litellmrtk-db` and `litellmrtk-router`):
| `litellmrtk-9router` | `20128` | `8383` | **this stack** |
| `litellmrtk-router` (LiteLLM) | `4000` | `8083` | this stack |
| `9rtk-router` | `20128` | `8081` | 9RTKSync |
| `ominirtk-router` | `20128` | `8082` | OminiRTkSync |
| `omnirtk-router` | `20128` | `8082` | OmniRTkSync |

Two consequences worth stating plainly:

Expand Down Expand Up @@ -68,7 +68,7 @@ $ python3 tools/registra_gateways.py --conferir
modelos de gateway registrados neste proxy:
gateway-omniroute-granite
model = openai/openrouter/ibm-granite/granite-4.2-8b
api_base = http://ominirtk-router:20128/v1
api_base = http://omnirtk-router:20128/v1
credencial = gateway-cred-omniroute
gateway-9router-gemini
model = openai/gemini/gemini-3.8-flash
Expand Down Expand Up @@ -101,7 +101,7 @@ second, **inference** network:

```
$ docker network inspect rtk-inference-net --format '{{range .Containers}}{{.Name}} {{end}}'
ominirtk-router 9rtk-router litellmrtk-router
omnirtk-router 9rtk-router litellmrtk-router
```

Only routers join it. The synchronizers stay out, because they have no business
Expand All @@ -116,7 +116,7 @@ name resolves:
```
$ docker exec litellmrtk-router python3 -c "..."
RESOLVE 9rtk-router -> 172.24.0.2
RESOLVE ominirtk-router -> 172.24.0.3
RESOLVE omnirtk-router -> 172.24.0.3
RESOLVE litellmrtk-db -> 172.22.0.2
```

Expand Down Expand Up @@ -148,7 +148,7 @@ still holds:
```
$ docker exec 9rtk-sync getent hosts <nome>
RESOLVE 9rtk-router
nao-resolve ominirtk-router (rc=2)
nao-resolve omnirtk-router (rc=2)
nao-resolve litellmrtk-router (rc=2)
```

Expand All @@ -162,8 +162,8 @@ rather than by hostname:

```
$ docker exec 9rtk-sync python3 -c "..."
ALCANCA ominirtk-router gestao 172.21.0.2 20128
bloqueado ominirtk-router inferencia 172.24.0.3 20128 ( TimeoutError )
ALCANCA omnirtk-router gestao 172.21.0.2 20128
bloqueado omnirtk-router inferencia 172.24.0.3 20128 ( TimeoutError )
```

The management address answers; the inference address times out. Note which way
Expand Down Expand Up @@ -191,7 +191,7 @@ the network with it. The price is that it has to exist **before** the first `up`
All three repositories carry the target:

```bash
make rede-de-inferencia # em 9RTKSync, OminiRTkSync e LiteLlmRTKSync
make rede-de-inferencia # em 9RTKSync, OmniRTkSync e LiteLlmRTKSync
```

```
Expand All @@ -215,7 +215,7 @@ docker compose -f docker-compose.example.yml --env-file .env up -d
```

This one command now gives you the whole chain — proxy, database, **9Router** and
synchronizer. Bring up the OminiRTkSync stack too only if you want the OmniRoute
synchronizer. Bring up the OmniRTkSync stack too only if you want the OmniRoute
leg.

Before it runs, fill `INITIAL_PASSWORD` and `JWT_SECRET` in `.env`. They have no
Expand Down Expand Up @@ -355,7 +355,7 @@ On a clean install, where neither the credential nor the model exists yet:
+ credencial 'gateway-cred-9router' criada a partir de NINEROUTER_API_KEY
+ modelo 'gateway-9router-gemini' criado → openai/gemini/gemini-3.8-flash

[omniroute] → http://ominirtk-router:20128/v1
[omniroute] → http://omnirtk-router:20128/v1
chave OMNIROUTE_API_KEY = sk-cf2*** (len=35)
+ credencial 'gateway-cred-omniroute' criada a partir de OMNIROUTE_API_KEY
+ modelo 'gateway-omniroute-granite' criado → openai/openrouter/ibm-granite/granite-4.2-8b
Expand All @@ -371,7 +371,7 @@ prefix and length. It is idempotent; a second run writes nothing:
= credencial 'gateway-cred-9router' já existe
= modelo 'gateway-9router-gemini' já existe → http://litellmrtk-9router:20128/v1
= credencial 'gateway-cred-omniroute' já existe
= modelo 'gateway-omniroute-granite' já existe → http://ominirtk-router:20128/v1
= modelo 'gateway-omniroute-granite' já existe → http://omnirtk-router:20128/v1
0 modelo(s) criado(s) nesta execução.
```

Expand Down Expand Up @@ -504,7 +504,7 @@ break all three.
On OmniRoute the log carries the id of the key instead:

```
$ docker logs -t ominirtk-router --since 3m | grep -iE "apiKeyId|POST /v1/chat"
$ docker logs -t omnirtk-router --since 3m | grep -iE "apiKeyId|POST /v1/chat"
2026-09-13T20:04:07.123211341Z [SKILLS_INJECTION] {"apiKeyId":"11e94a34-efa5-4b9c-8c84-3e8c243633df",...}
2026-09-13T20:04:07.136928716Z ... "msg":"📥 POST /v1/chat/completions | openrouter/ibm-granite/granite-4.2-8b | 1 msgs"
```
Expand All @@ -518,7 +518,7 @@ If the answer survives the gateway being down, it never came from the gateway.

```
$ docker stop 9rtk-router
9rtk-router parado. na rede de inferencia agora: ominirtk-router litellmrtk-router
9rtk-router parado. na rede de inferencia agora: omnirtk-router litellmrtk-router

HTTP=500 tempo=26.016s
{"error":{"message":"litellm.InternalServerError: OpenAIException - Connection error..
Expand Down Expand Up @@ -737,7 +737,7 @@ hostname, como `litellmrtk-db` e `litellmrtk-router`):
| `litellmrtk-9router` | `20128` | `8383` | **esta stack** |
| `litellmrtk-router` (LiteLLM) | `4000` | `8083` | esta stack |
| `9rtk-router` | `20128` | `8081` | 9RTKSync |
| `ominirtk-router` | `20128` | `8082` | OminiRTkSync |
| `omnirtk-router` | `20128` | `8082` | OmniRTkSync |

Duas consequências que vale dizer sem rodeio:

Expand Down Expand Up @@ -777,12 +777,12 @@ configura aqui, e o próprio proxy avisa: `Available Model Group Fallbacks=None`

Cada stack tem sua rede de **gestão**. Os routers que precisam ser alcançados
*entre* stacks compartilham uma segunda rede, de **inferência**
(`ominirtk-router 9rtk-router litellmrtk-router`). Os sincronizadores ficam de
(`omnirtk-router 9rtk-router litellmrtk-router`). Os sincronizadores ficam de
fora — e, desde a seção 0, o `litellmrtk-9router` também: ele é de casa, e pô-lo
numa rede compartilhada só o exporia aos vizinhos sem dar nada em troca.

**A de inferência existe para o tráfego passar entre stacks.** Sem ela o proxy nem
nomeia um gateway que mora em outro lugar; com ela, `ominirtk-router ->
nomeia um gateway que mora em outro lugar; com ela, `omnirtk-router ->
172.24.0.3` resolve de dentro do container.

**A de gestão é por onde a cadeia do 9Router passa agora.** Medido em
Expand All @@ -804,7 +804,7 @@ litellmrtksync-net 172.24.0.5 # e só essa
stacks na rede default do Docker, o mesmo nome curto resolvia para stacks
diferentes e ninguém sabia a qual gateway um painel havia se conectado. É esse o
defeito que a separação previne, e ele continua prevenido: `9rtk-sync` resolve
`9rtk-router` e **não** resolve `ominirtk-router` nem `litellmrtk-router`.
`9rtk-router` e **não** resolve `omnirtk-router` nem `litellmrtk-router`.
**Quem juntar tudo numa rede só traz a ambiguidade de volta.**

### O que esse isolamento é, e o que não é
Expand Down
2 changes: 1 addition & 1 deletion docs/wiki/Dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ failed. `CRON_ENABLED=0` stops the automatic loop; the **Run now** button and th
working.

**One cycle trigger, not two.** The header's **Sync now** button and the scheduler card's **Run
now** submit the same route, `/acoes/cron`, as they do in 9RTKSync and OminiRTkSync. There used
now** submit the same route, `/acoes/cron`, as they do in 9RTKSync and OmniRTkSync. There used
to be a second route that ran the same cycle outside the scheduler, and a cycle triggered that
way never appeared in the history the screen shows.

Expand Down
4 changes: 2 additions & 2 deletions docs/wiki/Home.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ republishes these pages automatically. Editing a page directly here will be over

## Why this one is different from its siblings

9RTKSync and OminiRTkSync **renew** OAuth credentials: their gateway keeps tokens that expire in
9RTKSync and OmniRTkSync **renew** OAuth credentials: their gateway keeps tokens that expire in
a SQLite file on disk, and nobody notices when one dies until a request fails.

LiteLLM has no consumer OAuth. Its credentials are provider API keys in the model catalogue and
Expand Down Expand Up @@ -70,7 +70,7 @@ writing to the table would skip the invariants the proxy enforces.
| Project | Gateway | Panel port (host) |
| :--- | :--- | :--- |
| [9RTKSync](https://github.com/pathbit/9RTKSync) | [9Router](https://github.com/decolua/9router) | `9091` |
| [OminiRTkSync](https://github.com/pathbit/OminiRTkSync) | [OmniRoute](https://github.com/diegosouzapw/OmniRoute) | `9092` |
| [OmniRTkSync](https://github.com/pathbit/OmniRTkSync) | [OmniRoute](https://github.com/diegosouzapw/OmniRoute) | `9092` |
| **LiteLlmRTKSync** | [LiteLLM](https://github.com/BerriAI/litellm) | `9093` |

All three listen on **port 9090 inside their container**; the published host ports differ so the
Expand Down
2 changes: 1 addition & 1 deletion docs/wiki/Installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ Same for the gateways.
| OmniRoute | `20128` | `8082` |
| LiteLLM | `4000` | `8083` |
| 9RTKSync panel | `9090` | `9091` |
| OminiRTkSync panel | `9090` | `9092` |
| OmniRTkSync panel | `9090` | `9092` |
| LiteLlmRTKSync panel | `9090` | `9093` |

The article stack (`claudegravity`) keeps **`20128`**, 9Router's default port.
Expand Down
2 changes: 1 addition & 1 deletion tests/test_cartoes_do_painel.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Os seis cartões existem, sempre, e na mesma ordem dos painéis irmãos.

A regra do dono é uma só: "as telas têm cards diferentes entre LiteLlmRTKSync,
OminiRTkSync e etc, tem que ter todos os cards iguais". O que pode mudar é o
OmniRTkSync e etc, tem que ter todos os cards iguais". O que pode mudar é o
CONTEÚDO; a existência e a ordem, não.

Isto já quebrou uma vez em silêncio: este painel listava chaves virtuais e
Expand Down
2 changes: 1 addition & 1 deletion tests/test_documentacao_bate_com_codigo.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

- a página de saída de rede do 9RTKSync descrevia o gateway do irmão, com
campos que não existem aqui;
- a wiki do OminiRTkSync anunciava uma resposta de `/healthz` que o servidor
- a wiki do OmniRTkSync anunciava uma resposta de `/healthz` que o servidor
nunca devolveu;
- o README do LiteLlmRTKSync citava uma rota `POST /api/sync` inexistente.
"""
Expand Down
10 changes: 5 additions & 5 deletions tests/test_identidade_do_projeto.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"""Este projeto nasceu portando código dos irmãos, e sobrou identidade deles.

O log deste sincronizador se chamava `ominirtksync.log`, o logger respondia por
`ominirtksync` e o diretório de fallback era `~/.ominirtksync/logs` — tudo
copiado do OminiRTkSync e nunca renomeado. Nada disso quebra teste nem aparece
O log deste sincronizador se chamava `omnirtksync.log`, o logger respondia por
`omnirtksync` e o diretório de fallback era `~/.omnirtksync/logs` — tudo
copiado do OmniRTkSync e nunca renomeado. Nada disso quebra teste nem aparece
em revisão: aparece quando alguém procura o log deste serviço e encontra o nome
do outro, ou pior, quando os dois rodam na mesma máquina, apontam para o mesmo
`~/.ominirtksync/logs` e escrevem no mesmo arquivo.
`~/.omnirtksync/logs` e escrevem no mesmo arquivo.

A prosa que compara este projeto com os irmãos é bem-vinda e continua valendo —
o que não pode é um **identificador** deste projeto carregar o nome do outro.
Expand All @@ -21,7 +21,7 @@
# Nome de outro projeto usado como identificador: arquivo, logger, diretório.
SUSPEITOS = re.compile(
r"""(?ix)
(["'][^"']*\b(?:omini|nine)rtksync[^"']*["']) # string com o nome do irmão
(["'][^"']*\b(?:omni|nine)rtksync[^"']*["']) # string com o nome do irmão
"""
)

Expand Down
4 changes: 2 additions & 2 deletions tests/test_identidade_isolada.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@

PROIBIDOS = [
(re.compile(r"#[0-9a-fA-F]{6}\b"), "cor fora de identidade.py"),
(re.compile(r"9RTKSync|OminiRTKSync|OminiRTkSync|LiteLlmRTKSync"), "nome de produto literal"),
(re.compile(r"9RTKSync|OmniRTKSync|OmniRTkSync|LiteLlmRTKSync"), "nome de produto literal"),
(re.compile(r"9Router|OmniRoute|LiteLLM"), "nome de gateway literal"),
(re.compile(r"9rtk-|ominirtk-|litellmrtk-"), "prefixo de container literal"),
(re.compile(r"9rtk-|omnirtk-|litellmrtk-"), "prefixo de container literal"),
(
re.compile(r"bi-lightning-charge-fill|bi-signpost-split-fill|bi-speedometer2"),
"ícone do produto literal",
Expand Down
10 changes: 5 additions & 5 deletions tests/test_irmaos_identicos.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Os módulos comuns são o MESMO texto nos três irmãos, byte a byte.

9RTKSync, OminiRTkSync e LiteLlmRTKSync são a mesma aplicação. O que muda entre
9RTKSync, OmniRTkSync e LiteLlmRTKSync são a mesma aplicação. O que muda entre
eles é cor, nome, logo, identidade visual e a quem cada um se conecta — e isso
mora inteiro em `identidade.py` (o que é identidade) e em `gateway.py` (o que é
domínio do gateway). Todo o resto tem de ser o mesmo arquivo, ou "são quase
Expand Down Expand Up @@ -40,7 +40,7 @@
RAIZ = pathlib.Path(__file__).resolve().parent.parent

# Nome do diretório de cada irmão ao lado deste repositório.
IRMAOS = ("9RTKSync", "OminiRTkSync", "LiteLlmRTKSync")
IRMAOS = ("9RTKSync", "OmniRTkSync", "LiteLlmRTKSync")

# Módulos que têm de ser o mesmo texto nos três, relativos a `src/<pacote>/`.
IDENTICOS = (
Expand Down Expand Up @@ -99,9 +99,9 @@

# O que cada produto troca de propósito, e que não conta como divergência.
IDENTIDADE = (
("nine_rtksync", "omini_rtksync", "litellm_rtksync"),
("9RTKSync", "OminiRTKSync", "OminiRTkSync", "LiteLlmRTKSync"),
("9rtk", "ominirtk", "litellmrtk"),
("nine_rtksync", "omni_rtksync", "litellm_rtksync"),
("9RTKSync", "OmniRTKSync", "OmniRTkSync", "LiteLlmRTKSync"),
("9rtk", "omnirtk", "litellmrtk"),
("9Router", "OmniRoute", "LiteLLM"),
)

Expand Down
2 changes: 1 addition & 1 deletion tests/test_rotas_simetricas.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import unittest

RAIZ = pathlib.Path(__file__).resolve().parent.parent
IRMAOS = ("9RTKSync", "OminiRTkSync", "LiteLlmRTKSync")
IRMAOS = ("9RTKSync", "OmniRTkSync", "LiteLlmRTKSync")

# Rotas que o produto TEM de servir, independentemente do gateway por trás.
# Entram aqui as que algum pedido explícito criou -- assim o teste também
Expand Down
6 changes: 3 additions & 3 deletions tools/registra_gateways.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

O OmniRoute continua sendo de OUTRA stack, e para ele o encontro acontece
numa segunda rede, `rtk-inference-net`, declarada como externa nos composes
e que só os gateways e este proxy compartilham. Sem ela, `ominirtk-router`
e que só os gateways e este proxy compartilham. Sem ela, `omnirtk-router`
sequer resolve de dentro do container do LiteLLM.

docker network create rtk-inference-net
Expand Down Expand Up @@ -153,11 +153,11 @@
"nome": "omniroute",
"model_name": f"{PREFIXO}omniroute-granite",
"model": "openai/openrouter/ibm-granite/granite-4.2-8b",
"api_base": "http://ominirtk-router:20128/v1",
"api_base": "http://omnirtk-router:20128/v1",
"api_base_host": "http://127.0.0.1:8082/v1",
"env_var": "OMNIROUTE_API_KEY",
"credential_name": f"{PREFIXO}cred-omniroute",
"container": "ominirtk-router",
"container": "omnirtk-router",
},
]

Expand Down
2 changes: 1 addition & 1 deletion tools/valida_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
VARIAVEIS_DE_TERCEIROS = {
# litellm/proxy/hooks/__init__.py:31, LiteLLM 1.102.0
"LEGACY_MULTI_INSTANCE_RATE_LIMITING",
# Flag dos composes do 9RTKSync e do OminiRTkSync. A pagina de encadeamento
# Flag dos composes do 9RTKSync e do OmniRTkSync. A pagina de encadeamento
# precisa nomea-la porque ela e uma armadilha: quem le `REQUIRE_API_KEY=false`
# conclui que nao precisa de chave, enquanto o 9Router autoriza por peer e
# responde 401 a qualquer vizinho de rede. O nome nunca vai existir no fonte
Expand Down
Loading