{"id":43471070,"url":"https://github.com/r9s-ai/open-next-router","last_synced_at":"2026-04-23T04:01:22.915Z","repository":{"id":335020492,"uuid":"1142153155","full_name":"r9s-ai/open-next-router","owner":"r9s-ai","description":"A lightweight, DSL-driven LLM gateway for routing, patching provider quirks, and normalizing APIs across channels","archived":false,"fork":false,"pushed_at":"2026-04-16T11:09:50.000Z","size":16326,"stargazers_count":18,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-04-16T13:12:27.162Z","etag":null,"topics":["ai","anthropic","api-gateway","docker","dsl","gemini","go","golang","llm","llm-gateway","nginx","one-api","openai","openai-api","proxy","sse"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/r9s-ai.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-01-26T02:40:37.000Z","updated_at":"2026-04-16T11:09:26.000Z","dependencies_parsed_at":null,"dependency_job_id":"d174893a-deb0-43e6-b8c2-caee51c1f615","html_url":"https://github.com/r9s-ai/open-next-router","commit_stats":null,"previous_names":["r9s-ai/open-next-router"],"tags_count":85,"template":false,"template_full_name":null,"purl":"pkg:github/r9s-ai/open-next-router","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/r9s-ai%2Fopen-next-router","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/r9s-ai%2Fopen-next-router/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/r9s-ai%2Fopen-next-router/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/r9s-ai%2Fopen-next-router/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/r9s-ai","download_url":"https://codeload.github.com/r9s-ai/open-next-router/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/r9s-ai%2Fopen-next-router/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32165201,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-23T02:19:40.750Z","status":"ssl_error","status_checked_at":"2026-04-23T02:17:55.737Z","response_time":53,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","anthropic","api-gateway","docker","dsl","gemini","go","golang","llm","llm-gateway","nginx","one-api","openai","openai-api","proxy","sse"],"created_at":"2026-02-03T07:03:31.085Z","updated_at":"2026-04-23T04:01:22.781Z","avatar_url":"https://github.com/r9s-ai.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# onr (open-next-router)\n\n**A lightweight, DSL-driven LLM gateway for routing, patching provider quirks, and enforcing consistent APIs across channels**\n\n[![CI](https://github.com/r9s-ai/open-next-router/actions/workflows/ci.yml/badge.svg)](https://github.com/r9s-ai/open-next-router/actions/workflows/ci.yml)\n[![Go Version](https://img.shields.io/github/go-mod/go-version/r9s-ai/open-next-router)](https://github.com/r9s-ai/open-next-router/blob/main/go.mod)\n[![Go Reference](https://pkg.go.dev/badge/github.com/r9s-ai/open-next-router.svg)](https://pkg.go.dev/github.com/r9s-ai/open-next-router)\n[![Go Report Card](https://goreportcard.com/badge/github.com/r9s-ai/open-next-router)](https://goreportcard.com/report/github.com/r9s-ai/open-next-router)\n[![GitHub Release](https://img.shields.io/github/v/release/r9s-ai/open-next-router)](https://github.com/r9s-ai/open-next-router/releases)\n[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)\n[![Docs](https://img.shields.io/badge/docs-official-0ea5e9)](https://onr.mintlify.app/)\n\n[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/r9s-ai/open-next-router)\n[![zread](https://img.shields.io/badge/Ask_Zread-_.svg?style=flat\u0026color=00b0aa\u0026labelColor=000000\u0026logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTQuOTYxNTYgMS42MDAxSDIuMjQxNTZDMS44ODgxIDEuNjAwMSAxLjYwMTU2IDEuODg2NjQgMS42MDE1NiAyLjI0MDFWNC45NjAxQzEuNjAxNTYgNS4zMTM1NiAxLjg4ODEgNS42MDAxIDIuMjQxNTYgNS42MDAxSDQuOTYxNTZDNS4zMTUwMiA1LjYwMDEgNS42MDE1NiA1LjMxMzU2IDUuNjAxNTYgNC45NjAxVjIuMjQwMUM1LjYwMTU2IDEuODg2NjQgNS4zMTUwMiAxLjYwMDEgNC45NjE1NiAxLjYwMDFaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00Ljk2MTU2IDEwLjM5OTlIMi4yNDE1NkMxLjg4ODEgMTAuMzk5OSAxLjYwMTU2IDEwLjY4NjQgMS42MDE1NiAxMS4wMzk5VjEzLjc1OTlDMS42MDE1NiAxNC4xMTM0IDEuODg4MSAxNC4zOTk5IDIuMjQxNTYgMTQuMzk5OUg0Ljk2MTU2QzUuMzE1MDIgMTQuMzk5OSA1LjYwMTU2IDE0LjExMzQgNS42MDE1NiAxMy43NTk5VjExLjAzOTlDNS42MDE1NiAxMC42ODY0IDUuMzE1MDIgMTAuMzk5OSA0Ljk2MTU2IDEwLjM5OTlaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik0xMy43NTg0IDEuNjAwMUgxMS4wMzg0QzEwLjY4NSAxLjYwMDEgMTAuMzk4NCAxLjg4NjY0IDEwLjM5ODQgMi4yNDAxVjQuOTYwMUMxMC4zOTg0IDUuMzEzNTYgMTAuNjg1IDUuNjAwMSAxMS4wMzg0IDUuNjAwMUgxMy43NTg0QzE0LjExMTkgNS42MDAxIDE0LjM5ODQgNS4zMTM1NiAxNC4zOTg0IDQuOTYwMVYyLjI0MDFDMTQuMzk4NCAxLjg4NjY0IDE0LjExMTkgMS42MDAxIDEzLjc1ODQgMS42MDAxWiIgZmlsbD0iI2ZmZiIvPgo8cGF0aCBkPSJNNCAxMkwxMiA0TDQgMTJaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00IDEyTDEyIDQiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgo8L3N2Zz4K\u0026logoColor=ffffff)](https://zread.ai/r9s-ai/open-next-router)\n[![Telegram](https://img.shields.io/badge/Telegram-Join-blue?logo=telegram)](https://t.me/opennextrouter)\n[![Discord](https://img.shields.io/badge/Discord-Join-5865F2?logo=discord\u0026logoColor=white)](https://discord.gg/HBM67dP8)\n\n\u003c/div\u003e\n\n---\n\nopen-next-router (ONR) is a lightweight, DSL-driven LLM gateway that routes requests, applies compatibility patches, and normalizes behavior across providers and channels.\n\n## Why ONR?\n\n- **Atomic, nginx-like DSL**: runtime behavior is explicitly declared in DSL loaded from `config/onr.conf` (typically including `config/providers/*.conf`, routing, auth headers, transforms, SSE parsing, usage extraction).\n- **Fast provider onboarding and patching**: fix provider quirks by editing a `.conf` file instead of changing and redeploying code.\n- **Hot reload**: reload `onr.yaml` / `keys.yaml` / `models.yaml` / provider DSL files via SIGHUP; provider DSL can also auto-reload by file watch (opt-in).\n- **No hidden magic**: compatibility is opt-in via directives (e.g. `req_map`, `resp_map`, `sse_parse`, `json_del`, `set_header`) rather than implicit heuristics.\n- **Streaming-aware normalization**: handle SSE framing and provider-specific streaming semantics while keeping a stable client-facing API.\n- **Operational visibility**: one-line request logs with optional usage/cost extraction help you debug channels and control spend.\n\n## DSL (nginx-like, atomic) at a glance\n\nAll runtime behavior (routing, auth headers, request/response transforms, SSE parsing, usage extraction, etc.) is explicitly described\nby DSL loaded from `config/onr.conf`, which typically includes files under `config/providers/*.conf`.\n\n```conf\n# Minimal: route + auth\n# config/providers/acme.conf\nsyntax \"next-router/0.1\";\n\nprovider \"acme\" {\n  defaults {\n    upstream_config {\n      base_url = \"https://api.example.com\";\n    }\n    auth {\n      auth_bearer;\n    }\n  }\n\n  match api = \"chat.completions\" {\n    upstream {\n      set_path \"/v1/chat/completions\";\n    }\n    response {\n      resp_passthrough;\n    }\n  }\n}\n```\n\n```conf\n# Extended: opt-in compatibility transforms (examples)\n# config/providers/anthropic.conf\nsyntax \"next-router/0.1\";\n\nprovider \"anthropic\" {\n  defaults {\n    upstream_config {\n      base_url = \"https://api.anthropic.com\";\n    }\n    auth {\n      auth_header_key \"x-api-key\";\n    }\n    request {\n      set_header \"anthropic-version\" \"2023-06-01\";\n    }\n  }\n\n  # OpenAI /v1/chat/completions -\u003e Anthropic /v1/messages (non-stream)\n  match api = \"chat.completions\" stream = false {\n    request {\n      req_map openai_chat_to_anthropic_messages;\n      json_del \"$.stream_options\";\n    }\n    upstream {\n      set_path \"/v1/messages\";\n    }\n    response {\n      resp_map anthropic_to_openai_chat;\n    }\n  }\n\n  # OpenAI /v1/chat/completions -\u003e Anthropic /v1/messages (streaming)\n  match api = \"chat.completions\" stream = true {\n    request {\n      req_map openai_chat_to_anthropic_messages;\n      json_del \"$.stream_options\";\n    }\n    upstream {\n      set_path \"/v1/messages\";\n    }\n    response {\n      sse_parse anthropic_to_openai_chunks;\n    }\n  }\n}\n```\n\nMore examples: `config/providers/` • Full reference: [DSL_SYNTAX.md](https://github.com/r9s-ai/open-next-router/blob/main/DSL_SYNTAX.md)\n\n## Quick Start\n\n### One-click install (Linux, recommended)\n\nInstall latest runtime release as a systemd service:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/r9s-ai/open-next-router/main/tools/install_onr_service.sh | sudo bash -s -- \\\n  --mode service \\\n  --api-key 'change-me'\n```\n\nHealth check:\n\n```bash\ncurl -sS http://127.0.0.1:3300/v1/models -H \"Authorization: Bearer change-me\"\n```\n\nOther install modes:\n\n- Docker: `--mode docker`\n- Docker Compose: `--mode docker-compose`\n\n### Run from source (development)\n\n1) Prepare configs\n\n- Copy `config/onr.example.yaml` -\u003e `onr.yaml`\n- Copy `config/keys.example.yaml` -\u003e `keys.yaml`\n- Copy `config/models.example.yaml` -\u003e `models.yaml`\n\n2) Run\n\n```bash\ncd open-next-router\ngo run ./cmd/onr --config ./onr.yaml\n```\n\n3) Reload (nginx-like)\n\nAfter editing `onr.yaml` / `keys.yaml` / `models.yaml` / provider DSL files, you can reload runtime configs by sending SIGHUP:\n\n```bash\ngo run ./cmd/onr --config ./onr.yaml -s reload\n```\n\nThis uses `server.pid_file` (default: `/var/run/onr.pid`).\n\nOptional: enable provider DSL auto-reload by file watch (disabled by default):\n\n```yaml\nproviders:\n  dir: \"./config/providers\"\n  auto_reload:\n    enabled: true\n    debounce_ms: 300\n```\n\n4) Test config (nginx-like)\n\nTest configs without starting the server:\n\n```bash\n# default config path\ngo run ./cmd/onr -t\n\n# specify config file (flag)\ngo run ./cmd/onr -t -c ./onr.yaml\n\n# specify config file (positional)\ngo run ./cmd/onr -t ./onr.yaml\n```\n\n5) Bundle provider DSL into a single file\n\nValidate the provider DSL source first, then write a self-contained merged file:\n\n```bash\n# resolve provider source from onr.yaml and write providers.conf\ngo run ./cmd/onr-pack -c ./onr.yaml -o ./dist/providers.conf\n\n# or bundle a specific DSL source directly\ngo run ./cmd/onr-pack --providers ./config/onr.conf --out ./dist/providers.conf\n```\n\nThe command validates the provider DSL before writing. If validation fails, no output file is generated.\n\n6) Setup Git hooks with prek\n\n```bash\n# install git hooks (force-replace if pre-commit hooks already exist)\nprek install -f\n\n# run all hooks manually\nprek run --all-files\n```\n\n## Docker Compose\n\nCreate runtime config files first:\n\n```bash\ncd open-next-router\ncp config/onr.example.yaml onr.yaml\ncp config/keys.example.yaml keys.yaml\ncp config/models.example.yaml models.yaml\ndocker compose up --build\n```\n\n3) Call\n\n```bash\ncurl -sS http://127.0.0.1:3300/v1/chat/completions \\\n  -H \"Authorization: Bearer change-me\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"model\":\"gpt-4o-mini\",\"messages\":[{\"role\":\"user\",\"content\":\"hello\"}]}'\n```\n\n## Architecture (high level)\n\n```text\n                    ┌─────────────────────────────────────────┐\n                    │              open-next-router           │\n                    │                (Gin server)             │\n                    └─────────────────────────────────────────┘\n                                      │\n                                      │ Auth: Bearer / x-api-key\n                                      ▼\n┌─────────────────────────────────────────────────────────────────────────────┐\n│                              Request Pipeline                               │\n│                                                                             │\n│  ┌──────────────────────────────┐  ┌───────────────────────┐  ┌───────────┐ │\n│  │ request_id + access log      │  │ provider selection    │  │ DSL exec  │ │\n│  │ optional traffic dump        │  │ 1) x-onr-provider     │  │ (phases)  │ │\n│  └──────────────────────────────┘  │ 2) models.yaml        │  └───────────┘ │\n│                                    └───────────────────────┘                │\n│                                                                             │\n│  DSL phases (explicit, nginx-like):                                         │\n│  - upstream_config: base_url (and per-channel overrides)                    │\n│  - auth: auth header shape, optional OAuth exchange + bearer injection      │\n│  - request: header/query/json patching, request mapping (compat mode)       │\n│  - upstream: set_path/set_query, proxy settings                             │\n│  - response: passthrough / resp_map / sse_parse (streaming normalization)   │\n│  - error: error_map                                                         │\n│  - metrics/pricing: usage_extract, cost estimation (optional)               │\n│                                                                             │\n│                                                                             │\n│                         ┌──────────────────────────┐                        │\n│                         │        upstream          │                        │\n│                         │ provider base_url + path │                        │\n│                         └──────────────────────────┘                        │\n└─────────────────────────────────────────────────────────────────────────────┘\n                                      │\n                                      ▼\n                    ┌──────────────────────────────────────────┐\n                    │                Upstream APIs             │\n                    │   OpenAI-compatible and native provider  │\n                    │   APIs (Anthropic, Gemini, etc.)         │\n                    └──────────────────────────────────────────┘\n\nObservability:\n- [ONR] one-line request log\n    • base (always): ts, status, latency, client_ip, method, path\n    • fields (always): request_id, latency_ms\n    • routing (when available): api, provider, provider_source, model, stream\n    • upstream (when available): upstream_status, finish_reason\n    • usage (when available): usage_stage, input_tokens, output_tokens, total_tokens, cache_read_tokens, cache_write_tokens, billable_input_tokens\n    • usage extras (when produced by `usage_fact`): flattened fields such as `cache_write_ttl_5m_tokens`, `cache_write_ttl_1h_tokens`, `server_tool_web_search_calls`\n    • cost (when enabled/available): cost_total, cost_input, cost_output, cost_cache_read, cost_cache_write, cost_multiplier, cost_model, cost_channel, cost_unit\n        - usage_stage=upstream: usage returned by upstream\n        - usage_stage=estimate_*: best-effort estimation when upstream usage is missing/zero\n\n- Optional traffic dump (file-based)\n    • META\n    • ORIGIN\n    • UPSTREAM\n    • PROXY\n\nConfig reload:\n- Send SIGHUP to reload `onr.yaml` / `keys.yaml` / `models.yaml` / `config/onr.conf` and its included DSL files (nginx-like)\n- Optional: enable `providers.auto_reload.enabled=true` to watch the resolved provider DSL source directory and auto-reload included DSL files\n```\n\n## Auth\n\n- Recommended: `Authorization: Bearer \u003cACCESS_KEY_FROM_KEYS_YAML\u003e`\n- Compatible headers: `x-api-key` / `x-goog-api-key`\n- `onr.yaml` can omit `auth` entirely when using `keys.yaml` `access_keys`\n- Optional legacy mode: `auth.api_key` (master key in `onr.yaml`)\n\n### URI-like token key (onr:v1?)\n\nIf your client can only set a single API key and cannot add custom headers, you can use a URI-like token key:\n\n**No-sig mode (editable):**\n\n`onr:v1?k=\u003cACCESS_KEY\u003e\u0026{query_without_k}`\n\nor\n\n`onr:v1?k64=\u003cbase64url(ACCESS_KEY)\u003e\u0026{query_without_k64}`\n\nSupported query params:\n\n- `k` / `k64`: access key (required by default)\n- `p`: provider (optional)\n- `m`: model override (optional; always enforced)\n- `uk`: BYOK upstream key (optional; when set, ONR uses it directly to call upstream)\n\nOptional config to allow BYOK token without `k`/`k64` (default: false):\n\n```yaml\nauth:\n  token_key:\n    allow_byok_without_k: true\n```\n\nGenerate a token key:\n\n```bash\nmake build\nonr-admin token create \\\n  --config ./onr.yaml \\\n  --access-key-name client-a \\\n  --provider openai \\\n  --model gpt-4o-mini\n```\n\nMore details: see `docs/ACCESS_KEYS_CN.md`.\n\n## Upstream Keys (keys.yaml)\n\n### Plaintext\n\nYou can put plaintext keys in `keys.yaml` (not recommended for public repos).\n\n### Encrypted values (AES-256-GCM)\n\n`keys.yaml` supports encrypted values in this format:\n\n`ENC[v1:aesgcm:\u003cbase64(nonce+ciphertext)\u003e]`\n\nTo decrypt `ENC[...]` values, set `ONR_MASTER_KEY` (32 bytes or base64-encoded 32 bytes).\n\nTo generate an encrypted value:\n\n```bash\nexport ONR_MASTER_KEY='...'\necho -n 'sk-xxxx' | onr-admin crypto encrypt\n```\n\n### Env override (recommended for CI / docker / k8s)\n\nFor each key entry, you can override the value via environment variable:\n\n- If `name` is set: `ONR_UPSTREAM_KEY_\u003cPROVIDER\u003e_\u003cNAME\u003e`\n- Otherwise: `ONR_UPSTREAM_KEY_\u003cPROVIDER\u003e_\u003cINDEX\u003e` (1-based)\n\nExample:\n\n- `providers.openai.keys[0].name: key1` -\u003e `ONR_UPSTREAM_KEY_OPENAI_KEY1`\n\n## Access Keys (keys.yaml: access_keys)\n\n`keys.yaml` can also contain access keys for clients:\n\n```yaml\naccess_keys:\n  - name: \"client-a\"\n    value: \"ak-xxx\"\n    comment: \"iOS app\"\n```\n\nEnv override:\n\n- If `name` is set: `ONR_ACCESS_KEY_\u003cNAME\u003e` (e.g. `ONR_ACCESS_KEY_CLIENT_A`)\n- Otherwise: `ONR_ACCESS_KEY_\u003cINDEX\u003e` (1-based)\n\n## Admin CLI (onr-admin)\n\n`onr-admin` command usage is documented in:\n\n- `onr-admin/USAGE.md`\n\n## Multi-Module Layout\n\nThe repository now uses two Go modules:\n\n- `.` (onr runtime/server + onr-admin CLI)\n- `onr-core` (shared library for external ecosystem and internal reuse)\n\nFor local multi-module development, use the repository root `go.work`.\n\nQuick checks:\n\n```bash\n# onr\ngo test ./...\n\n# onr-core\n(cd onr-core \u0026\u0026 go test ./...)\n\n# onr-admin (included in root module)\ngo test ./...\n```\n\n### onr-core stable versioning for external consumers\n\n`onr-core` is released with dedicated submodule tags: `onr-core/vX.Y.Z`.\n\n```bash\ngo get github.com/r9s-ai/open-next-router/onr-core@v1.2.3\n```\n\n## Upstream HTTP Proxy (per provider)\n\nYou can configure an outbound HTTP proxy per upstream provider in `onr.yaml`:\n\n```yaml\nupstream_proxies:\n  by_provider:\n    openai: \"http://127.0.0.1:7890\"\n    anthropic: \"http://127.0.0.1:7891\"\n```\n\nSupported proxy URL schemes:\n\n- `http://` / `https://`\n- `socks5://` / `socks5h://` (optional user/pass: `socks5://user:pass@host:port`)\n\nOr override via environment variables:\n\n- `ONR_UPSTREAM_PROXY_OPENAI=http://127.0.0.1:7890`\n- `ONR_UPSTREAM_PROXY_ANTHROPIC=http://127.0.0.1:7891`\n\n## OAuth Token Persistence\n\nWhen provider DSL `auth` uses OAuth directives, ONR can persist exchanged access tokens to local files.\n\n```yaml\noauth:\n  token_persist:\n    enabled: true\n    dir: \"./run/oauth\"\n```\n\nEnvironment overrides:\n\n- `ONR_OAUTH_TOKEN_PERSIST_ENABLED=true|false`\n- `ONR_OAUTH_TOKEN_PERSIST_DIR=./run/oauth`\n\n## Provider Selection\n\n- Override: `x-onr-provider: \u003cprovider\u003e`\n\n## Gemini Native API (v1beta)\n\nIn addition to OpenAI-style endpoints, open-next-router supports a subset of Gemini native endpoints:\n\n- `POST /v1beta/models/{model}:generateContent`\n- `POST /v1beta/models/{model}:streamGenerateContent` (SSE; `alt=sse` will be added if missing)\n- `GET /v1beta/models` (Gemini-style output)\n\nExample (force provider selection via header):\n\n```bash\ncurl -sS http://127.0.0.1:3300/v1beta/models/gemini-2.0-flash:generateContent \\\n  -H \"Authorization: Bearer change-me\" \\\n  -H \"x-onr-provider: gemini\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"contents\":[{\"role\":\"user\",\"parts\":[{\"text\":\"hello\"}]}]}'\n```\n\n## Model Routing (models.yaml)\n\nYou can bind a model to one or more providers. If a model is bound to multiple providers,\nopen-next-router selects the provider using round-robin (per model).\n\nSelection priority:\n\n1) `x-onr-provider` header (force)\n2) `models.yaml` routing (per model round-robin)\n\n## Traffic Dump (files)\n\nEnable file-based traffic dump to capture request/response for debugging.\n\nConfiguration (config or env):\n\n- `traffic_dump.enabled` / `ONR_TRAFFIC_DUMP_ENABLED`\n- `traffic_dump.dir` / `ONR_TRAFFIC_DUMP_DIR`\n- `traffic_dump.file_path` / `ONR_TRAFFIC_DUMP_FILE_PATH` (template supports `{{.request_id}}`)\n- `traffic_dump.max_bytes` / `ONR_TRAFFIC_DUMP_MAX_BYTES`\n- `traffic_dump.mask_secrets` / `ONR_TRAFFIC_DUMP_MASK_SECRETS`\n- `traffic_dump.sections` / `ONR_TRAFFIC_DUMP_SECTIONS` (comma-separated allowlist; empty means all sections)\n\nCaptured sections (default: all; configurable via `traffic_dump.sections`):\n\n- `=== META ===`\n- `=== ORIGIN REQUEST ===`\n- `=== UPSTREAM REQUEST ===`\n- `=== UPSTREAM RESPONSE ===`\n- `=== PROXY RESPONSE ===`\n- `=== STREAM ===`\n\n## System Log (runtime)\n\nSystem logs are emitted to `stderr` in single-line text with a fixed prefix, and optional trailing KV fields.\n\nConfiguration (config or env):\n\n- `logging.level` (`debug` | `info` | `warn` | `error`)\n- `ONR_LOG_LEVEL`\n\nExample:\n\n```text\n[ONR] 2026/02/27 - 12:34:56 | INFO | startup | startup config loaded | config_path=./onr.yaml providers_path=./config/onr.conf providers_source_is_file=true keys_file=./keys.yaml models_file=./models.yaml\n[ONR] 2026/02/27 - 12:34:56 | INFO | startup | startup runtime flags | access_log_enabled=true access_log_target=stdout traffic_dump_enabled=false providers_auto_reload_enabled=false\n[ONR] 2026/02/27 - 12:34:56 | INFO | server | open-next-router listening | listen_url=http://127.0.0.1:3300\n```\n\nStartup summary includes key runtime status fields:\n\n- `config_path`\n- `providers_path` / `providers_source_is_file` / `keys_file` / `models_file`\n- `traffic_dump_enabled` / `traffic_dump_dir` / `traffic_dump_max_bytes`\n- `access_log_enabled` / `access_log_target`\n- `providers_auto_reload_enabled` / `providers_auto_reload_debounce_ms`\n- `listen_url` (server listening log)\n\n## Access Log Rotation\n\nBuilt-in access log rotation is optional and applies to file output (`logging.access_log_path`).\n\nConfiguration (config or env):\n\n- `logging.access_log_rotate.enabled` / `ONR_ACCESS_LOG_ROTATE_ENABLED`\n- `logging.access_log_rotate.max_size_mb` / `ONR_ACCESS_LOG_ROTATE_MAX_SIZE_MB`\n- `logging.access_log_rotate.max_backups` / `ONR_ACCESS_LOG_ROTATE_MAX_BACKUPS`\n- `logging.access_log_rotate.max_age_days` / `ONR_ACCESS_LOG_ROTATE_MAX_AGE_DAYS`\n- `logging.access_log_rotate.compress` / `ONR_ACCESS_LOG_ROTATE_COMPRESS`\n\nNotes:\n\n- When `logging.access_log_rotate.enabled=true`, `logging.access_log_path` must be non-empty.\n- Rotation triggers on day boundary (local time) or when the file size threshold is exceeded.\n\n# Partnership\n\n\u003ca href=\"https://llmapis.com?source=https%3A%2F%2Fgithub.com%2Fr9s-ai%2Fopen-next-router\" target=\"_blank\"\u003e\u003cimg src=\"https://llmapis.com/api/badge/r9s-ai/open-next-router\" alt=\"LLMAPIS\" width=\"60\" /\u003e\u003c/a\u003e\n\n_Partnership with [https://llmapis.com](https://llmapis.com) - Discover more AI tools and resources_\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fr9s-ai%2Fopen-next-router","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fr9s-ai%2Fopen-next-router","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fr9s-ai%2Fopen-next-router/lists"}