Metadata-Version: 2.4
Name: spanway
Version: 1.106.0
Requires-Dist: fastuuid>=0.14.0,<1.0
Requires-Dist: filelock>=3.16.1,<4.0
Requires-Dist: httpx[http2]>=0.28.0,<1.0
Requires-Dist: openai>=2.20.0,<4.0.0
Requires-Dist: python-dotenv>=1.0.0,<2.0
Requires-Dist: pyyaml>=6.0.3,<7.0
Requires-Dist: packaging>=24.0
Requires-Dist: importlib-metadata>=8.0.0,<9.0
Requires-Dist: tiktoken>=0.8.0,<1.0 ; python_full_version < '3.14'
Requires-Dist: tiktoken>=0.12.0,<1.0 ; python_full_version >= '3.14'
Requires-Dist: tokenizers>=0.21.0,<1.0
Requires-Dist: huggingface-hub>=0.34.0,<2.0
Requires-Dist: click>=8.0.0,<9.0
Requires-Dist: jinja2>=3.1.6,<4.0
Requires-Dist: aiohttp>=3.14.2,<4.0
Requires-Dist: async-timeout>=4.0.3,<6.0 ; python_full_version < '3.11'
Requires-Dist: pydantic>=2.11.0,<3.0.0 ; python_full_version < '3.14'
Requires-Dist: pydantic>=2.12.0,<3.0.0 ; python_full_version >= '3.14'
Requires-Dist: pydantic-settings>=2.14.1,<3.0
Requires-Dist: jsonschema>=4.0.0,<5.0
Requires-Dist: boto3>=1.43.1,<2.0
Requires-Dist: typing-extensions>=4.13.0,<5.0
Requires-Dist: aws-sdk-bedrock-runtime[awscrt]>=0.10.0,<0.12.0 ; python_full_version >= '3.12' and extra == 'bedrock-realtime'
Requires-Dist: diskcache>=5.6.3,<6.0 ; extra == 'caching'
Requires-Dist: rich>=13.9.4,<14.0 ; extra == 'cli'
Requires-Dist: pyyaml>=6.0.3,<7.0 ; extra == 'cli'
Requires-Dist: requests>=2.32.0,<3.0 ; extra == 'cli'
Requires-Dist: inquirerpy>=0.3.4,<1.0 ; extra == 'cli'
Requires-Dist: keyring>=25.6.0,<26.0 ; extra == 'cli'
Requires-Dist: tomlkit>=0.13.3,<1.0 ; extra == 'cli'
Requires-Dist: deepeval ; extra == 'evals'
Requires-Dist: deepteam ; python_full_version < '3.14' and extra == 'evals'
Requires-Dist: prisma>=0.11.0,<1.0 ; extra == 'extra-proxy'
Requires-Dist: psycopg>=3.2,<4.0 ; extra == 'extra-proxy'
Requires-Dist: psycopg-binary>=3.2,<4.0 ; extra == 'extra-proxy'
Requires-Dist: azure-identity>=1.25.2,<2.0 ; extra == 'extra-proxy'
Requires-Dist: azure-keyvault-secrets>=4.10.0,<5.0 ; extra == 'extra-proxy'
Requires-Dist: google-cloud-kms>=2.24.2,<3.0 ; extra == 'extra-proxy'
Requires-Dist: google-cloud-iam>=2.19.1,<3.0 ; extra == 'extra-proxy'
Requires-Dist: resend>=2.23.0,<3.0 ; extra == 'extra-proxy'
Requires-Dist: redisvl>=0.4.1,<1.0 ; extra == 'extra-proxy'
Requires-Dist: a2a-sdk>=1.1.0,<2.0 ; extra == 'extra-proxy'
Requires-Dist: google-cloud-aiplatform>=1.133.0,<2.0 ; extra == 'google'
Requires-Dist: grpcio==1.78.0 ; extra == 'grpc'
Requires-Dist: mcp>=2.2.0,<3 ; extra == 'mcp'
Requires-Dist: httpx2>=2.5.0,<3 ; extra == 'mcp'
Requires-Dist: pydantic>=2.12.0,<3 ; extra == 'mcp'
Requires-Dist: mlflow>=3.11.1,<4.0 ; extra == 'mlflow'
Requires-Dist: gunicorn>=23.0.0,<24.0 ; extra == 'proxy'
Requires-Dist: uvicorn>=0.33.0,<1.0 ; extra == 'proxy'
Requires-Dist: granian>=2.7.4,<3.0 ; extra == 'proxy'
Requires-Dist: uvloop>=0.22.1,<1.0 ; sys_platform != 'win32' and extra == 'proxy'
Requires-Dist: fastapi>=0.136.3,<1.0 ; extra == 'proxy'
Requires-Dist: starlette>=1.0.1,<2.0 ; extra == 'proxy'
Requires-Dist: backoff>=2.2.1,<3.0 ; extra == 'proxy'
Requires-Dist: pyyaml>=6.0.3,<7.0 ; extra == 'proxy'
Requires-Dist: rq>=2.7.0,<3.0 ; extra == 'proxy'
Requires-Dist: orjson>=3.11.6,<4.0 ; extra == 'proxy'
Requires-Dist: hiredis>=3.0.0,<4.0 ; extra == 'proxy'
Requires-Dist: apscheduler>=3.11.2,<4.0 ; extra == 'proxy'
Requires-Dist: fastapi-sso>=0.19.0,<1.0 ; extra == 'proxy'
Requires-Dist: pyjwt>=2.13.0,<3.0 ; extra == 'proxy'
Requires-Dist: python-multipart>=0.0.27,<1.0 ; extra == 'proxy'
Requires-Dist: cryptography>=49.0.0,<51.0 ; extra == 'proxy'
Requires-Dist: pynacl>=1.6.2,<2.0 ; extra == 'proxy'
Requires-Dist: websockets>=15.0.1,<16.0 ; extra == 'proxy'
Requires-Dist: boto3>=1.43.1,<2.0 ; extra == 'proxy'
Requires-Dist: azure-identity>=1.25.2,<2.0 ; extra == 'proxy'
Requires-Dist: azure-storage-blob>=12.28.0,<13.0 ; extra == 'proxy'
Requires-Dist: mcp>=2.2.0,<3 ; extra == 'proxy'
Requires-Dist: httpx2>=2.5.0,<3 ; extra == 'proxy'
Requires-Dist: pydantic>=2.12.0,<3 ; extra == 'proxy'
Requires-Dist: spanway-proxy-extras==0.4.108 ; extra == 'proxy'
Requires-Dist: spanway-enterprise[billing]==0.1.76 ; extra == 'proxy'
Requires-Dist: restrictedpython>=8.5,<9.0 ; extra == 'proxy'
Requires-Dist: rich>=13.9.4,<14.0 ; extra == 'proxy'
Requires-Dist: inquirerpy>=0.3.4,<1.0 ; extra == 'proxy'
Requires-Dist: tomlkit>=0.13.3,<1.0 ; extra == 'proxy'
Requires-Dist: polars>=1.38.1,<2.0 ; extra == 'proxy'
Requires-Dist: soundfile>=0.12.1,<1.0 ; extra == 'proxy'
Requires-Dist: pyroscope-io>=0.8.16,<1.0 ; sys_platform != 'win32' and extra == 'proxy'
Requires-Dist: expression>=5.6.0,<6.0 ; extra == 'proxy'
Requires-Dist: google-cloud-aiplatform>=1.133.0,<2.0 ; extra == 'proxy-runtime'
Requires-Dist: google-cloud-speech>=2.40.0,<3.0 ; extra == 'proxy-runtime'
Requires-Dist: google-genai>=1.37.0,<2.0 ; extra == 'proxy-runtime'
Requires-Dist: anthropic[vertex]>=0.84.0,<1.0 ; extra == 'proxy-runtime'
Requires-Dist: grpcio==1.78.0 ; extra == 'proxy-runtime'
Requires-Dist: prometheus-client>=0.20.0,<1.0 ; extra == 'proxy-runtime'
Requires-Dist: langfuse>=4.7,<5.0 ; extra == 'proxy-runtime'
Requires-Dist: opentelemetry-api==1.33.1 ; extra == 'proxy-runtime'
Requires-Dist: opentelemetry-sdk==1.33.1 ; extra == 'proxy-runtime'
Requires-Dist: opentelemetry-exporter-otlp==1.33.1 ; extra == 'proxy-runtime'
Requires-Dist: opentelemetry-instrumentation-fastapi==0.54b1 ; extra == 'proxy-runtime'
Requires-Dist: ddtrace>=4.8.2,<5.0 ; extra == 'proxy-runtime'
Requires-Dist: sentry-sdk>=2.21.0,<3.0 ; extra == 'proxy-runtime'
Requires-Dist: mangum>=0.17.0,<1.0 ; extra == 'proxy-runtime'
Requires-Dist: azure-ai-contentsafety>=1.0.0,<2.0 ; extra == 'proxy-runtime'
Requires-Dist: azure-storage-file-datalake>=12.20.0,<13.0 ; extra == 'proxy-runtime'
Requires-Dist: pypdf>=6.16.1,<7.0 ; extra == 'proxy-runtime'
Requires-Dist: llm-sandbox>=0.3.39,<1.0 ; extra == 'proxy-runtime'
Requires-Dist: detect-secrets>=1.5.0,<2.0 ; extra == 'proxy-runtime'
Requires-Dist: python3-saml>=1.16.0,<2.0 ; extra == 'saml'
Requires-Dist: semantic-router>=0.1.15,<1.0 ; python_full_version < '3.14' and extra == 'semantic-router'
Requires-Dist: aurelio-sdk>=0.0.19,<1.0 ; python_full_version < '3.14' and extra == 'semantic-router'
Requires-Dist: nvidia-riva-client>=2.15.0 ; extra == 'stt-nvidia-riva'
Requires-Dist: soundfile>=0.12.1 ; extra == 'stt-nvidia-riva'
Requires-Dist: audioread>=3.0.1 ; extra == 'stt-nvidia-riva'
Requires-Dist: numpy>=1.26.0 ; extra == 'stt-nvidia-riva'
Requires-Dist: google-cloud-speech>=2.40.0,<3.0 ; extra == 'stt-vertex-chirp'
Requires-Dist: numpydoc>=1.8.0,<2.0 ; extra == 'utils'
Provides-Extra: bedrock-realtime
Provides-Extra: caching
Provides-Extra: cli
Provides-Extra: evals
Provides-Extra: extra_proxy
Provides-Extra: google
Provides-Extra: grpc
Provides-Extra: mcp
Provides-Extra: mlflow
Provides-Extra: proxy
Provides-Extra: proxy-runtime
Provides-Extra: saml
Provides-Extra: semantic-router
Provides-Extra: stt-nvidia-riva
Provides-Extra: stt-vertex-chirp
Provides-Extra: utils
License-File: LICENSE
License-File: THIRD_PARTY_NOTICES.md
Summary: Library to easily interface with LLM API providers
Author: sgourneni
License-Expression: LicenseRef-Spanway-Commercial
Requires-Python: >=3.10, <3.15
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Documentation, https://docs.spanway.ai
Project-URL: Homepage, https://spanway.ai
Project-URL: Repository, https://github.com/sgourneni/spanway-ai

# Spanway

Spanway is a self-hosted, commercially licensed AI gateway for platform teams. One OpenAI-compatible API in front of more than a hundred model providers, with virtual keys, teams and organisations, budgets and rate limits, routing with fallbacks, guardrails, caching, prompt management, an MCP gateway, tracing, and an Admin UI. The enterprise layer adds the license system, custom roles, a developer portal with approvals, compliance and data governance, billing and invoicing, evals and red teaming, and the hosted Cloud edition

This repository is private. Spanway is not open source. See `LICENSE` and `enterprise/LICENSE.md`

## Layout

| Path | What it is |
| --- | --- |
| `spanway/` | The gateway: SDK, proxy server, router, integrations |
| `enterprise/` | The Spanway enterprise package (license-gated features) |
| `spanway-proxy-extras/` | Database schema and migrations |
| `spanway-rust/` | Rust crates used by the Python bridge and by Lens |
| `lens/` | Lens, the tracing and investigation worker and its UI library |
| `third_party/` | Vendored upstream packages with their notices (DeepEval, DeepTeam, the admin agent and its MCP connector) |
| `ui/spanway-dashboard/` | The Admin UI (Next.js) |
| `docs/` | docs.spanway.ai (Docusaurus) |
| `website/` | spanway.ai (Next.js static export) |
| `helm/`, `terraform/`, `backend/`, `gateway/`, `migrations/`, `docker/` | Deployment assets and the per-service images |
| `scripts/fork/` | Fork tooling: `sync_upstream.py`, `rebrand.py`, `sync_vendored.py`, `issue_license.py` |

## Run it locally

```bash
uv sync --extra proxy --extra extra_proxy --extra evals
export SPANWAY_MASTER_KEY=sk-...            # generate your own
export SPANWAY_LICENSE=...                   # issue one with scripts/fork/issue_license.py
export DATABASE_URL=postgresql://spanway@localhost:5432/spanway
python spanway/proxy/proxy_cli.py --config spanway/proxy/dev_config.yaml --use_v2_migration_resolver
```

The Admin UI dev server is `npm run dev` in `ui/spanway-dashboard` on port 3000. The docs site is `npm run start` in `docs`, the website is `npm run dev` in `website`

## Build and release

Pushing a tag `vX.Y.Z` runs `.github/workflows/release.yml`: native wheels for Linux x86_64, Linux arm64 and macOS arm64 plus the pure packages, PyPI through trusted publishing, the container images under `ghcr.io/sgourneni/`, and the Helm charts under `oci://ghcr.io/sgourneni/charts`. Local builds: `uv build`, `docker build -f Dockerfile .`

### Verifying release images and charts

Every image and Helm chart the release workflow pushes is signed with cosign keyless signing and carries an SPDX SBOM attestation. There is no long-lived public key to download. The signing certificate is issued by Sigstore to the release workflow's GitHub OIDC identity, so verification checks that identity instead

```bash
IMAGE=ghcr.io/sgourneni/spanway-ai:v1.0.0
IDENTITY='^https://github\.com/sgourneni/spanway-ai/\.github/workflows/release\.yml@refs/tags/v'
ISSUER=https://token.actions.githubusercontent.com

cosign verify --certificate-identity-regexp "$IDENTITY" --certificate-oidc-issuer "$ISSUER" "$IMAGE"
cosign verify-attestation --type spdxjson \
  --certificate-identity-regexp "$IDENTITY" --certificate-oidc-issuer "$ISSUER" "$IMAGE"
```

Charts verify the same way with their OCI reference, for example `ghcr.io/sgourneni/charts/spanway:<chart version>`. A signature made by any other workflow, branch or fork fails the identity check

## Keeping up with upstream

The gateway, Lens, the admin agent and the MCP connector are forks. `upstream.json` records the upstream commit each one is synced to and `scripts/fork/sync_upstream.py --name <upstream> --dry-run` shows what a sync would apply. Vendored Python packages refresh with `scripts/fork/sync_vendored.py`. See `scripts/fork/UPSTREAM_SYNC.md`

## Checks

`make check` runs the full lint and type gates. Unit tests live under `tests/unit` mirroring the source tree, end-to-end tests under `tests/e2e`. See `AGENTS.md` for the engineering conventions every change follows

