airelays 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. airelays-0.2.0/.github/workflows/ci.yml +37 -0
  2. airelays-0.2.0/.github/workflows/publish-pypi.yml +58 -0
  3. airelays-0.2.0/.gitignore +20 -0
  4. airelays-0.2.0/ACKNOWLEDGEMENTS.md +5 -0
  5. airelays-0.2.0/CHANGELOG.md +19 -0
  6. airelays-0.2.0/CODE_OF_CONDUCT.md +12 -0
  7. airelays-0.2.0/CONTRIBUTING.md +25 -0
  8. airelays-0.2.0/DISCLAIMER.md +9 -0
  9. airelays-0.2.0/LICENSE +21 -0
  10. airelays-0.2.0/PKG-INFO +258 -0
  11. airelays-0.2.0/README.md +229 -0
  12. airelays-0.2.0/SECURITY.md +20 -0
  13. airelays-0.2.0/airelay.example.env +11 -0
  14. airelays-0.2.0/docs/README.md +24 -0
  15. airelays-0.2.0/docs/adr/0001-no-silent-degradation-truncation-or-budget-invention.md +36 -0
  16. airelays-0.2.0/docs/adr/0002-chatgpt-subscription-backend-compatibility-boundary.md +30 -0
  17. airelays-0.2.0/docs/adr/0003-local-config-and-default-deny-relay-security.md +49 -0
  18. airelays-0.2.0/docs/adr/README.md +5 -0
  19. airelays-0.2.0/docs/api.md +185 -0
  20. airelays-0.2.0/docs/architecture.md +86 -0
  21. airelays-0.2.0/docs/configuration.md +129 -0
  22. airelays-0.2.0/docs/faq.md +54 -0
  23. airelays-0.2.0/docs/getting-started.md +204 -0
  24. airelays-0.2.0/docs/security.md +97 -0
  25. airelays-0.2.0/docs/subscription-status.md +43 -0
  26. airelays-0.2.0/docs/troubleshooting.md +86 -0
  27. airelays-0.2.0/llms-full.txt +152 -0
  28. airelays-0.2.0/llms.txt +49 -0
  29. airelays-0.2.0/pyproject.toml +47 -0
  30. airelays-0.2.0/src/airelay/__init__.py +3 -0
  31. airelays-0.2.0/src/airelay/__main__.py +5 -0
  32. airelays-0.2.0/src/airelay/app.py +765 -0
  33. airelays-0.2.0/src/airelay/auth.py +737 -0
  34. airelays-0.2.0/src/airelay/backend.py +243 -0
  35. airelays-0.2.0/src/airelay/cli.py +574 -0
  36. airelays-0.2.0/src/airelay/config.py +396 -0
  37. airelays-0.2.0/src/airelay/html.py +252 -0
  38. airelays-0.2.0/src/airelay/security.py +314 -0
  39. airelays-0.2.0/src/airelay/store.py +243 -0
  40. airelays-0.2.0/src/airelay/terminal.py +47 -0
  41. airelays-0.2.0/src/airelay/traffic.py +108 -0
  42. airelays-0.2.0/src/airelay/transforms.py +981 -0
  43. airelays-0.2.0/tests/conftest.py +10 -0
  44. airelays-0.2.0/tests/test_auth_and_cli.py +305 -0
  45. airelays-0.2.0/tests/test_auth_login.py +134 -0
  46. airelays-0.2.0/tests/test_backend_and_app.py +594 -0
  47. airelays-0.2.0/tests/test_config.py +80 -0
  48. airelays-0.2.0/tests/test_transforms.py +556 -0
@@ -0,0 +1,37 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: ["main", "master"]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ python-version: ["3.11", "3.12"]
15
+ steps:
16
+ - name: Check out repository
17
+ uses: actions/checkout@v4
18
+
19
+ - name: Set up Python
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+
24
+ - name: Install package and test dependencies
25
+ run: python -m pip install --upgrade pip && python -m pip install -e .[dev]
26
+
27
+ - name: Run tests
28
+ run: pytest -q
29
+
30
+ - name: Byte-compile source and tests
31
+ run: python -m compileall src tests
32
+
33
+ - name: Build distribution artifacts
34
+ run: python -m build
35
+
36
+ - name: Check distribution metadata
37
+ run: python -m twine check dist/*
@@ -0,0 +1,58 @@
1
+ name: Publish PyPI
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ tags:
7
+ - "v*"
8
+
9
+ jobs:
10
+ publish:
11
+ runs-on: ubuntu-latest
12
+ permissions:
13
+ id-token: write
14
+ contents: read
15
+ environment:
16
+ name: pypi
17
+ steps:
18
+ - name: Check out repository
19
+ uses: actions/checkout@v4
20
+
21
+ - name: Set up Python
22
+ uses: actions/setup-python@v5
23
+ with:
24
+ python-version: "3.12"
25
+
26
+ - name: Verify tag matches project version
27
+ if: startsWith(github.ref, 'refs/tags/v')
28
+ run: |
29
+ python - <<'PY'
30
+ import os
31
+ import pathlib
32
+ import tomllib
33
+
34
+ payload = tomllib.loads(pathlib.Path("pyproject.toml").read_text(encoding="utf-8"))
35
+ version = payload["project"]["version"]
36
+ tag = os.environ["GITHUB_REF_NAME"]
37
+ expected = f"v{version}"
38
+ if tag != expected:
39
+ raise SystemExit(f"Tag {tag!r} does not match project version {expected!r}.")
40
+ PY
41
+
42
+ - name: Install project and build tooling
43
+ run: python -m pip install --upgrade pip build twine -e .[dev]
44
+
45
+ - name: Run test suite
46
+ run: pytest -q
47
+
48
+ - name: Compile sources
49
+ run: python -m compileall src tests
50
+
51
+ - name: Build distribution artifacts
52
+ run: python -m build
53
+
54
+ - name: Verify distribution metadata
55
+ run: python -m twine check dist/*
56
+
57
+ - name: Publish to PyPI
58
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,20 @@
1
+ .DS_Store
2
+
3
+ .venv/
4
+ .pytest_cache/
5
+ .mypy_cache/
6
+ .ruff_cache/
7
+ __pycache__/
8
+ *.py[cod]
9
+
10
+ .coverage
11
+ coverage.xml
12
+ htmlcov/
13
+
14
+ .airelay/
15
+ .openai-endpoint/
16
+ logs/
17
+
18
+ build/
19
+ dist/
20
+ *.egg-info/
@@ -0,0 +1,5 @@
1
+ # Acknowledgements
2
+
3
+ - The public Codex CLI repository informed the compatible login and auth-storage model reused here.
4
+ - The observed subscription-backend behavior defined the current compatibility boundary.
5
+ - AIRelay is an independent third-party project. Provider and product names are used only to describe compatibility and upstream behavior.
@@ -0,0 +1,19 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0
4
+
5
+ - Renamed the project surface to `AIRelay`, with package name `airelay` and CLI command `airelay`.
6
+ - Added `airelay init`, `airelay status`, and `airelay token rotate` to make first-run setup and local token management explicit.
7
+ - Added a local config file at `~/.config/airelay/config.toml` with `AIRELAY_*` overrides and legacy `OPENAI_ENDPOINT_*` fallback support.
8
+ - Added default bearer-token protection for `/v1/*` and `/no-tools/v1/*`.
9
+ - Added in-memory per-IP request rate limits, concurrent-request caps, and temporary blocks after repeated invalid tokens.
10
+ - Added protected `GET /v1/relay/status` diagnostics and reduced public `GET /healthz` to a minimal liveness payload.
11
+ - Added bounded local upload limits with explicit `413` failures instead of unbounded buffering.
12
+ - Made bearer-token bootstrap explicit by default: `airelay init` creates the token, while `airelay serve` fails fast if bearer auth is enabled and no token is configured.
13
+ - Clarified relay-token setup and client usage in the CLI output, startup banner, and user documentation.
14
+ - Corrected auth readiness so API-key-only state is not reported as request-ready.
15
+ - Moved upstream auth storage to AIRelay-owned state instead of reusing Codex-owned auth storage.
16
+ - Removed `codex_home` from the public AIRelay configuration surface.
17
+ - Added structured security events to the JSONL traffic log.
18
+ - Updated the landing page, docs, and API guidance to use the AIRelay token as the client API key.
19
+ - Added GitHub Actions workflows for CI and PyPI publishing preparation.
@@ -0,0 +1,12 @@
1
+ # Code Of Conduct
2
+
3
+ This project expects direct, respectful, and technically grounded collaboration.
4
+
5
+ Participants should:
6
+
7
+ - focus on evidence and reproducible behavior
8
+ - avoid harassment, intimidation, and personal attacks
9
+ - assume good faith while still challenging weak technical claims directly
10
+ - keep security-sensitive details out of public discussion when responsible disclosure is warranted
11
+
12
+ Maintainers may remove content or participation that undermines a safe and productive collaboration environment.
@@ -0,0 +1,25 @@
1
+ # Contributing
2
+
3
+ ## Development Setup
4
+
5
+ Contributor setup from a local checkout:
6
+
7
+ ```bash
8
+ python -m pip install -e .[dev]
9
+ pytest -q
10
+ python -m compileall src tests
11
+ ```
12
+
13
+ ## Project Rules
14
+
15
+ - preserve the explicit compatibility boundary
16
+ - do not silently widen support claims
17
+ - do not add truncation or hidden coercion for files, modalities, or token accounting
18
+ - prefer explicit errors over compatibility theater
19
+ - keep AIRelay auth storage independent from Codex or any other external tool state
20
+
21
+ ## Pull Request Expectations
22
+
23
+ - include tests for behavioral changes
24
+ - update docs when API behavior changes
25
+ - update ADRs when a durable engineering rule changes
@@ -0,0 +1,9 @@
1
+ # Disclaimer
2
+
3
+ AIRelay is an independent third-party project. It is not affiliated with, endorsed by, or sponsored by OpenAI or any other model provider.
4
+
5
+ Provider and product names are used only to describe compatibility targets, client shapes, and upstream protocol behavior.
6
+
7
+ AIRelay is designed for a single user operating a local relay for personal convenience. It is not presented as a shared, pooled, multi-user, or resale service.
8
+
9
+ You are responsible for reviewing and complying with the terms, policies, and usage limits that apply to any upstream account or subscription you use with AIRelay.
airelays-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Laurent-Philippe Albou
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,258 @@
1
+ Metadata-Version: 2.4
2
+ Name: airelays
3
+ Version: 0.2.0
4
+ Summary: AIRelay: an independent OpenAI-compatible local relay for single-user subscription-backed access.
5
+ Author: Laurent-Philippe Albou
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Keywords: ai,gateway,openai-compatible,relay,subscription
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Environment :: Console
11
+ Classifier: Framework :: FastAPI
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Requires-Python: >=3.11
18
+ Requires-Dist: fastapi>=0.115.0
19
+ Requires-Dist: httpx>=0.28.0
20
+ Requires-Dist: keyring>=25.5.0
21
+ Requires-Dist: python-multipart>=0.0.20
22
+ Requires-Dist: uvicorn>=0.35.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: build>=1.2.0; extra == 'dev'
25
+ Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
26
+ Requires-Dist: pytest>=8.3.0; extra == 'dev'
27
+ Requires-Dist: twine>=6.1.0; extra == 'dev'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # AIRelay
31
+
32
+ `AIRelay` is an independent local OpenAI-compatible HTTP server backed by a ChatGPT subscription login that AIRelay stores independently. It exposes the verified OpenAI-shaped routes this upstream can support, protects the local relay with its own bearer token, and logs every transit to hourly JSONL files.
33
+
34
+ `AIRelay` does not require a user-supplied OpenAI platform API key for upstream inference. Instead, it uses the same upstream ChatGPT login protocol that Codex uses while keeping AIRelay auth storage separate from Codex storage. Clients that call `AIRelay` should use the relay bearer token as the local client credential they present to AIRelay.
35
+
36
+ ## Independence And Intended Use
37
+
38
+ - AIRelay is an independent third-party project. It is not affiliated with, endorsed by, or sponsored by OpenAI.
39
+ - Provider and product names are used only to describe compatibility targets and upstream behavior.
40
+ - AIRelay is designed for a single user running a local relay for personal convenience.
41
+ - AIRelay is not presented as a shared, pooled, multi-user, or resale service.
42
+ - You are responsible for complying with the terms and usage policies that apply to any upstream account or subscription you use with AIRelay.
43
+
44
+ See [DISCLAIMER.md](DISCLAIMER.md) for the short project notice.
45
+
46
+ ## Quick Start
47
+
48
+ ```bash
49
+ python -m pip install .
50
+ airelay init
51
+ airelay login
52
+ airelay serve --port 8080
53
+ ```
54
+
55
+ If you are installing from a published package instead of a source checkout, use `python -m pip install airelay`.
56
+
57
+ Smoke test the public and protected surfaces:
58
+
59
+ ```bash
60
+ curl http://127.0.0.1:8080/healthz
61
+ curl http://127.0.0.1:8080/v1/relay/status \
62
+ -H 'authorization: Bearer YOUR_AIRELAY_TOKEN'
63
+ ```
64
+
65
+ Inspect the resolved relay and upstream-auth state at any point:
66
+
67
+ ```bash
68
+ airelay status
69
+ ```
70
+
71
+ CLI status and setup commands default to readable terminal output. Use `--json` on `airelay init`, `airelay status`, `airelay logout`, `airelay token show`, or `airelay token rotate` when you need machine-readable output for automation.
72
+
73
+ Point your client at:
74
+
75
+ ```text
76
+ http://127.0.0.1:8080/v1
77
+ ```
78
+
79
+ Use the token generated by `airelay init` as the client credential when you point an OpenAI-compatible SDK at AIRelay. Standard OpenAI SDKs will then send `Authorization: Bearer <relay-token>` automatically.
80
+
81
+ If you want to provide the relay token yourself instead of using the default token file, launch the server with:
82
+
83
+ ```bash
84
+ AIRELAY_BEARER_TOKEN='YOUR_AIRELAY_TOKEN' airelay serve --port 8080
85
+ ```
86
+
87
+ or point AIRelay at a specific token file:
88
+
89
+ ```bash
90
+ airelay serve --bearer-token-file /path/to/relay-token --port 8080
91
+ ```
92
+
93
+ ## What AIRelay Does
94
+
95
+ - Uses the same upstream login protocol as Codex browser login and device-code login.
96
+ - Stores upstream auth under AIRelay-owned state instead of reusing `~/.codex`.
97
+ - Generates and persists a separate relay bearer token for client-to-relay access.
98
+ - Protects `/v1/*` and `/no-tools/v1/*` with bearer auth, per-IP rate limits, concurrent-request caps, and temporary blocks after repeated bad tokens.
99
+ - Exposes OpenAI-compatible routes for:
100
+ - `GET /v1/models`
101
+ - `GET /v1/subscription/status`
102
+ - `GET /v1/account/rate_limits`
103
+ - `POST /v1/completions`
104
+ - `POST /v1/responses`
105
+ - `POST /v1/chat/completions`
106
+ - `POST /v1/files`
107
+ - `GET /v1/files`
108
+ - `GET /v1/files/{file_id}`
109
+ - `GET /v1/files/{file_id}/content`
110
+ - `DELETE /v1/files/{file_id}`
111
+ - `POST /v1/conversations`
112
+ - `GET /v1/conversations/{conversation_id}`
113
+ - `POST /v1/conversations/{conversation_id}`
114
+ - `DELETE /v1/conversations/{conversation_id}`
115
+ - `/no-tools/v1/models`
116
+ - `/no-tools/v1/completions`
117
+ - `/no-tools/v1/responses`
118
+ - `/no-tools/v1/chat/completions`
119
+ - Logs inbound requests, endpoint rejects, outbound responses, upstream requests, upstream responses, stream lines, and usage summaries to `logs/YYYY/MM/DD-HH.log`.
120
+
121
+ ## First-Run Flow
122
+
123
+ 1. `airelay init`
124
+ - writes `~/.config/airelay/config.toml` if it does not already exist
125
+ - creates `~/.airelay/relay-token` with `0600` permissions if a relay token is missing
126
+ - prints a formatted setup summary and reveals the token only when it was newly created
127
+ 2. `airelay login`
128
+ - creates an AIRelay-owned ChatGPT subscription session
129
+ 3. `airelay serve --port 8080`
130
+ - starts the protected local endpoint
131
+ - fails fast if bearer auth is enabled but no relay token is configured
132
+ - prints the client base URL, token file path, and the required `Authorization` header shape
133
+
134
+ You can show the current relay token at any time:
135
+
136
+ ```bash
137
+ airelay token show
138
+ ```
139
+
140
+ You can also rotate the relay token later:
141
+
142
+ ```bash
143
+ airelay token rotate
144
+ ```
145
+
146
+ ## Example Client Usage
147
+
148
+ Python with the OpenAI SDK:
149
+
150
+ ```python
151
+ from openai import OpenAI
152
+
153
+ client = OpenAI(
154
+ base_url="http://127.0.0.1:8080/v1",
155
+ api_key="YOUR_AIRELAY_TOKEN",
156
+ )
157
+
158
+ response = client.responses.create(
159
+ model="gpt-5.4-mini",
160
+ input="Summarize the purpose of AIRelay.",
161
+ )
162
+ print(response.output_text)
163
+ ```
164
+
165
+ Raw `curl`:
166
+
167
+ ```bash
168
+ curl http://127.0.0.1:8080/v1/responses \
169
+ -H 'authorization: Bearer YOUR_AIRELAY_TOKEN' \
170
+ -H 'content-type: application/json' \
171
+ -d '{
172
+ "model": "gpt-5.4-mini",
173
+ "input": "Summarize the purpose of AIRelay.",
174
+ "stream": false
175
+ }'
176
+ ```
177
+
178
+ Shell example with the OpenAI Python SDK environment variables:
179
+
180
+ ```bash
181
+ export OPENAI_BASE_URL='http://127.0.0.1:8080/v1'
182
+ export OPENAI_API_KEY="$(tr -d '\n' < ~/.airelay/relay-token)"
183
+ ```
184
+
185
+ ## Verified Compatibility Boundary
186
+
187
+ This server is intentionally explicit about what is and is not verified.
188
+
189
+ - Inference uses `https://chatgpt.com/backend-api/codex`.
190
+ - Subscription status uses `https://chatgpt.com/backend-api/wham/usage`.
191
+ - The upstream requires `stream=true`, so non-stream OpenAI responses are reconstructed locally from streamed event sequences.
192
+ - The upstream requires `store=false`, so requests that try to enable upstream storage are rejected with `422`.
193
+ - The upstream requires non-empty `instructions`, so the compatibility layer injects the minimal verified placeholder `"."` only when the caller omitted instructions entirely.
194
+ - Image input is supported.
195
+ - Text and JSON-like document input is supported by local inlining up to 1 MB.
196
+ - Local file uploads are capped at 32 MiB each and 256 MiB total by default.
197
+ - Audio input, embeddings, image generation, realtime sessions, and other unverified routes return explicit `501 unsupported_error`.
198
+
199
+ ## Security Defaults
200
+
201
+ - Listener default: `127.0.0.1:8080`
202
+ - Protected routes: `/v1/*` and `/no-tools/v1/*`
203
+ - Public routes: `/` and a minimal `GET /healthz`
204
+ - Protected diagnostics: `GET /v1/relay/status`
205
+ - Relay auth: bearer token required by default
206
+ - Token storage: `~/.airelay/relay-token`
207
+ - Default rate limit: `120` requests/minute with burst `40`
208
+ - Default concurrent request cap: `8` per IP
209
+ - Default repeated-auth-failure block: `8` bad attempts in `300` seconds -> `900` second block
210
+
211
+ See [Security](docs/security.md) for the full behavior.
212
+
213
+ ## Configuration
214
+
215
+ AIRelay reads configuration in this order:
216
+
217
+ 1. explicit CLI flags such as `--config`, `--port`, or `--auth-storage`
218
+ 2. `AIRELAY_*` environment variables
219
+ 3. legacy `OPENAI_ENDPOINT_*` environment variables where supported as a migration fallback
220
+ 4. `~/.config/airelay/config.toml`
221
+ 5. built-in defaults
222
+
223
+ `auth.storage = "auto"` prefers the AIRelay keyring namespace and falls back to `~/.airelay/auth.json` when keyring access is unavailable.
224
+
225
+ Important paths:
226
+
227
+ - config: `~/.config/airelay/config.toml`
228
+ - data dir: `~/.airelay`
229
+ - upstream auth fallback file: `~/.airelay/auth.json`
230
+ - logs: `~/.airelay/logs`
231
+ - relay token: `~/.airelay/relay-token`
232
+
233
+ To override the default token source at launch time:
234
+
235
+ - `AIRELAY_BEARER_TOKEN`
236
+ - `airelay serve --bearer-token-file /path/to/relay-token`
237
+
238
+ See [Configuration](docs/configuration.md) for field details and a sample config.
239
+
240
+ ## Publication Surface
241
+
242
+ - package name: `airelay`
243
+ - CLI command: `airelay`
244
+ - legacy CLI alias kept for migration: `openai-endpoint`
245
+ - Python package: `airelay`
246
+
247
+ ## Documentation
248
+
249
+ - [Getting Started](docs/getting-started.md)
250
+ - [Configuration](docs/configuration.md)
251
+ - [Security](docs/security.md)
252
+ - [Disclaimer](DISCLAIMER.md)
253
+ - [API Notes](docs/api.md)
254
+ - [Architecture](docs/architecture.md)
255
+ - [Subscription Status](docs/subscription-status.md)
256
+ - [FAQ](docs/faq.md)
257
+ - [Troubleshooting](docs/troubleshooting.md)
258
+ - [ADR Index](docs/adr/README.md)