Contributing to Janus¶
Thanks for your interest in contributing! This guide covers setup, development workflows, and how to extend Janus.
Development Setup¶
git clone https://github.com/amanverasia/Janus.git
cd Janus
python -m venv .venv
pip install -e ".[dev]"
Daily Commands¶
# Run all tests (never use bare 'pytest')
.venv/bin/python -m pytest
# Run a single test
.venv/bin/python -m pytest tests/unit/formats/test_openai.py::test_name -v
# Lint
.venv/bin/ruff check src/janus/ tests/
# Format check
.venv/bin/ruff format --check src/janus/ tests/
# Typecheck
.venv/bin/mypy src/janus/
# Start dev server
.venv/bin/janus serve --port 20128 --reload
# Preview docs locally
.venv/bin/mkdocs serve
Run ruff check, ruff format --check, and mypy before every commit. CI enforces all three.
Architecture Constraint¶
Janus uses a canonical intermediate model. The rule is simple:
formats/andproviders/never import or call each other — they only talk tocanonical/.
This gives 2N adapters instead of N² translators. Do not break this boundary. If you need a format to talk to a provider, you're doing it wrong — go through the canonical model.
Request Flow¶
client format → parse_request → CanonicalRequest → SaverPipeline.apply
→ budget check → FallbackHandler.resolve_attempts
→ per-attempt: build_upstream_request → upstream call → parse_upstream_response
→ CanonicalResponse → emit_response → record_usage (with cost)
On 429/5xx/auth/network errors, the account is cooled down and the next attempt is tried.
Adding a New Format Adapter¶
- Create
src/janus/formats/<name>.pyimplementing all six methods:parse_request,build_upstream_request,parse_upstream_response,emit_response,stream_parser,stream_emitter. - Register in the
FORMATSdict insrc/janus/api/routes.py.
Adding a New Provider Executor¶
- Create
src/janus/providers/<name>.pywith anasync call(payload, stream) -> RawResultmethod and anasync close()method. - Add a case to
_build_provider()insrc/janus/app.py. - If the provider's native format differs from its
api_type, update_resolve_format()insrc/janus/api/routes.py.
Adding a New Token Saver¶
- Implement the
TokenSaverprotocol (transform(req) -> CanonicalRequest) insrc/janus/tokensavers/. - Add construction logic to
reload_savers()insrc/janus/dashboard/reload.py. - Savers must be fail-safe — exceptions are caught by the pipeline and logged, never breaking the request.
Maintainer Scripts¶
The scripts/ directory holds one-off maintenance tools. They are not installed with the
package — run them from a dev checkout with pip install -e ".[dev]" so janus imports resolve.
| Script | When to run |
|---|---|
scripts/generate_model_catalog.py |
After updating the upstream model catalog in Dashboard_For_Apis. Regenerates src/janus/inventory/data/model_catalog.json. Default source: sibling checkout at ../Dashboard_For_Apis/backend/src/services/model-catalog.ts; override with --source. |
scripts/seed_openrouter_pricing.py |
When refreshing OpenRouter per-model pricing in your local DB. Fetches the live catalog and writes rows to pricing_overrides. Use --dry-run to preview counts. |
# Regenerate model catalog (custom source path)
.venv/bin/python scripts/generate_model_catalog.py \
--source /path/to/model-catalog.ts
# Seed OpenRouter pricing into ~/.janus/janus.db
.venv/bin/python scripts/seed_openrouter_pricing.py
# Preview without writing
.venv/bin/python scripts/seed_openrouter_pricing.py --dry-run
PR Process¶
- Squash-merge PRs to
main. Branches are deleted after merge. - Write tests for all new functionality. Tests use
pytest-asynciowithasyncio_mode = "auto". - Provider tests mock httpx with
respx— no real network calls. - Integration tests use FastAPI ASGI transport (
httpx.ASGITransport) in-process. - Test fixtures live in
tests/fixtures/. - No code comments unless explicitly requested.
ruffwith line-length 100, rules: E, F, I, N, W, UP.mypy --strict— baredict/listmust be typed. UseX | YnotUnion. UseStrEnumnotstr, Enum.