siwc-proxy 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.
- siwc_proxy-0.2.0/.gitignore +222 -0
- siwc_proxy-0.2.0/LICENSE +21 -0
- siwc_proxy-0.2.0/PKG-INFO +201 -0
- siwc_proxy-0.2.0/README.md +162 -0
- siwc_proxy-0.2.0/docs/proxy.md +280 -0
- siwc_proxy-0.2.0/pyproject.toml +67 -0
- siwc_proxy-0.2.0/siwc_proxy/__init__.py +6 -0
- siwc_proxy-0.2.0/siwc_proxy/__main__.py +5 -0
- siwc_proxy-0.2.0/siwc_proxy/auth.py +371 -0
- siwc_proxy-0.2.0/siwc_proxy/chat.py +456 -0
- siwc_proxy-0.2.0/siwc_proxy/cli.py +312 -0
- siwc_proxy-0.2.0/siwc_proxy/config.py +86 -0
- siwc_proxy-0.2.0/siwc_proxy/constants.py +37 -0
- siwc_proxy-0.2.0/siwc_proxy/errors.py +89 -0
- siwc_proxy-0.2.0/siwc_proxy/responses.py +92 -0
- siwc_proxy-0.2.0/siwc_proxy/server.py +463 -0
- siwc_proxy-0.2.0/siwc_proxy/sse.py +77 -0
- siwc_proxy-0.2.0/siwc_proxy/store.py +199 -0
- siwc_proxy-0.2.0/tests/conftest.py +15 -0
- siwc_proxy-0.2.0/tests/smoke.py +26 -0
- siwc_proxy-0.2.0/tests/support.py +208 -0
- siwc_proxy-0.2.0/tests/test_chat.py +135 -0
- siwc_proxy-0.2.0/tests/test_cli.py +239 -0
- siwc_proxy-0.2.0/tests/test_compat_sdk.py +129 -0
- siwc_proxy-0.2.0/tests/test_login.py +201 -0
- siwc_proxy-0.2.0/tests/test_proxy.py +636 -0
- siwc_proxy-0.2.0/tests/test_tokens.py +227 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
*.lcov
|
|
51
|
+
.hypothesis/
|
|
52
|
+
.pytest_cache/
|
|
53
|
+
cover/
|
|
54
|
+
|
|
55
|
+
# Translations
|
|
56
|
+
*.mo
|
|
57
|
+
*.pot
|
|
58
|
+
|
|
59
|
+
# Django stuff:
|
|
60
|
+
*.log
|
|
61
|
+
local_settings.py
|
|
62
|
+
db.sqlite3
|
|
63
|
+
db.sqlite3-journal
|
|
64
|
+
|
|
65
|
+
# Flask stuff:
|
|
66
|
+
instance/
|
|
67
|
+
.webassets-cache
|
|
68
|
+
|
|
69
|
+
# Scrapy stuff:
|
|
70
|
+
.scrapy
|
|
71
|
+
|
|
72
|
+
# Sphinx documentation
|
|
73
|
+
docs/_build/
|
|
74
|
+
|
|
75
|
+
# PyBuilder
|
|
76
|
+
.pybuilder/
|
|
77
|
+
target/
|
|
78
|
+
|
|
79
|
+
# Jupyter Notebook
|
|
80
|
+
.ipynb_checkpoints
|
|
81
|
+
|
|
82
|
+
# IPython
|
|
83
|
+
profile_default/
|
|
84
|
+
ipython_config.py
|
|
85
|
+
|
|
86
|
+
# pyenv
|
|
87
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
88
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
89
|
+
# .python-version
|
|
90
|
+
|
|
91
|
+
# pipenv
|
|
92
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
93
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
94
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
95
|
+
# install all needed dependencies.
|
|
96
|
+
# Pipfile.lock
|
|
97
|
+
|
|
98
|
+
# uv
|
|
99
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
100
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
101
|
+
# commonly ignored for libraries.
|
|
102
|
+
# uv.lock
|
|
103
|
+
|
|
104
|
+
# poetry
|
|
105
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
106
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
107
|
+
# commonly ignored for libraries.
|
|
108
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
109
|
+
# poetry.lock
|
|
110
|
+
# poetry.toml
|
|
111
|
+
|
|
112
|
+
# pdm
|
|
113
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
114
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
115
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
116
|
+
# pdm.lock
|
|
117
|
+
# pdm.toml
|
|
118
|
+
.pdm-python
|
|
119
|
+
.pdm-build/
|
|
120
|
+
|
|
121
|
+
# pixi
|
|
122
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
123
|
+
# pixi.lock
|
|
124
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
125
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
126
|
+
.pixi/*
|
|
127
|
+
!.pixi/config.toml
|
|
128
|
+
|
|
129
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
130
|
+
__pypackages__/
|
|
131
|
+
|
|
132
|
+
# Celery stuff
|
|
133
|
+
celerybeat-schedule*
|
|
134
|
+
celerybeat.pid
|
|
135
|
+
|
|
136
|
+
# Redis
|
|
137
|
+
*.rdb
|
|
138
|
+
*.aof
|
|
139
|
+
*.pid
|
|
140
|
+
|
|
141
|
+
# RabbitMQ
|
|
142
|
+
mnesia/
|
|
143
|
+
rabbitmq/
|
|
144
|
+
rabbitmq-data/
|
|
145
|
+
|
|
146
|
+
# ActiveMQ
|
|
147
|
+
activemq-data/
|
|
148
|
+
|
|
149
|
+
# SageMath parsed files
|
|
150
|
+
*.sage.py
|
|
151
|
+
|
|
152
|
+
# Environments
|
|
153
|
+
.env
|
|
154
|
+
.envrc
|
|
155
|
+
.venv
|
|
156
|
+
env/
|
|
157
|
+
venv/
|
|
158
|
+
ENV/
|
|
159
|
+
env.bak/
|
|
160
|
+
venv.bak/
|
|
161
|
+
|
|
162
|
+
# Spyder project settings
|
|
163
|
+
.spyderproject
|
|
164
|
+
.spyproject
|
|
165
|
+
|
|
166
|
+
# Rope project settings
|
|
167
|
+
.ropeproject
|
|
168
|
+
|
|
169
|
+
# mkdocs/Zensical documentation
|
|
170
|
+
/site
|
|
171
|
+
|
|
172
|
+
# mypy
|
|
173
|
+
.mypy_cache/
|
|
174
|
+
.dmypy.json
|
|
175
|
+
dmypy.json
|
|
176
|
+
|
|
177
|
+
# Pyre type checker
|
|
178
|
+
.pyre/
|
|
179
|
+
|
|
180
|
+
# pytype static type analyzer
|
|
181
|
+
.pytype/
|
|
182
|
+
|
|
183
|
+
# Cython debug symbols
|
|
184
|
+
cython_debug/
|
|
185
|
+
|
|
186
|
+
# PyCharm
|
|
187
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
188
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
189
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
190
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
191
|
+
# .idea/
|
|
192
|
+
|
|
193
|
+
# Abstra
|
|
194
|
+
# Abstra is an AI-powered process automation framework.
|
|
195
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
196
|
+
# Learn more at https://abstra.io/docs
|
|
197
|
+
.abstra/
|
|
198
|
+
|
|
199
|
+
# Visual Studio Code
|
|
200
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore that
|
|
201
|
+
# can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
202
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer, you
|
|
203
|
+
# could uncomment the following to ignore the entire vscode folder
|
|
204
|
+
# .vscode/
|
|
205
|
+
# Temporary file for partial code execution
|
|
206
|
+
tempCodeRunnerFile.py
|
|
207
|
+
|
|
208
|
+
# Ruff stuff:
|
|
209
|
+
.ruff_cache/
|
|
210
|
+
|
|
211
|
+
# PyPI configuration file
|
|
212
|
+
.pypirc
|
|
213
|
+
|
|
214
|
+
# Marimo
|
|
215
|
+
marimo/_static/
|
|
216
|
+
marimo/_lsp/
|
|
217
|
+
__marimo__/
|
|
218
|
+
|
|
219
|
+
# Streamlit
|
|
220
|
+
.streamlit/secrets.toml
|
|
221
|
+
# Local OAuth credentials and host identity
|
|
222
|
+
.data/
|
siwc_proxy-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nyamkhuu Demberelsuren
|
|
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,201 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: siwc-proxy
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Local OpenAI-compatible API proxy for Sign in with ChatGPT token sharing
|
|
5
|
+
Project-URL: Homepage, https://github.com/d-nyamkhuu/siwc-proxy
|
|
6
|
+
Project-URL: Documentation, https://github.com/d-nyamkhuu/siwc-proxy/blob/main/docs/proxy.md
|
|
7
|
+
Project-URL: Source, https://github.com/d-nyamkhuu/siwc-proxy
|
|
8
|
+
Project-URL: Issues, https://github.com/d-nyamkhuu/siwc-proxy/issues
|
|
9
|
+
Author: Nyamkhuu Demberelsuren
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: chatgpt,llm,oauth,openai,proxy,sign-in-with-chatgpt
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Framework :: FastAPI
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: Internet :: Proxy Servers
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
25
|
+
Requires-Python: >=3.12
|
|
26
|
+
Requires-Dist: anyio>=4.4
|
|
27
|
+
Requires-Dist: authlib<1.8,>=1.7.2
|
|
28
|
+
Requires-Dist: click>=8.1
|
|
29
|
+
Requires-Dist: fastapi>=0.115
|
|
30
|
+
Requires-Dist: filelock>=3.16
|
|
31
|
+
Requires-Dist: httpx-sse>=0.4.3
|
|
32
|
+
Requires-Dist: httpx<0.29,>=0.28
|
|
33
|
+
Requires-Dist: itsdangerous>=2.2
|
|
34
|
+
Requires-Dist: joserfc>=1.6
|
|
35
|
+
Requires-Dist: platformdirs>=4.3
|
|
36
|
+
Requires-Dist: pydantic-settings>=2.7
|
|
37
|
+
Requires-Dist: uvicorn>=0.30
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
|
|
40
|
+
# siwc-proxy
|
|
41
|
+
|
|
42
|
+
A local **OpenAI-compatible API** backed by
|
|
43
|
+
[Sign in with ChatGPT token sharing](https://developers.openai.com/siwc/token-sharing-open-source).
|
|
44
|
+
Sign in once; the proxy keeps the ChatGPT plan credential fresh and serves
|
|
45
|
+
`/v1/chat/completions`, `/v1/responses`, and `/v1/models` to any OpenAI SDK,
|
|
46
|
+
framework, or tool that lets you set a base URL.
|
|
47
|
+
|
|
48
|
+
- **Sign-in and re-auth handled for you.** The proxy hosts its own OAuth callback,
|
|
49
|
+
registers its client on first use, and reuses that registration afterwards.
|
|
50
|
+
[Authlib](https://docs.authlib.org/) does PKCE, state, nonce, and ID-token checks.
|
|
51
|
+
- **Safe token refresh.** Rotating refresh tokens are refreshed once, even across
|
|
52
|
+
processes (file lock). Refreshed tokens are saved before they are checked, so
|
|
53
|
+
a failed check never loses the new refresh token.
|
|
54
|
+
A temporary outage never deletes credentials. An upstream 401 triggers one refresh and retry.
|
|
55
|
+
- **Works with ordinary client code.** `responses.create(input="hi")` without
|
|
56
|
+
`stream`/`store` works: the proxy streams upstream and aggregates the result.
|
|
57
|
+
Chat Completions is translated to Responses. Options that can't be honored,
|
|
58
|
+
such as `user` and `seed`, are dropped and listed in a response header.
|
|
59
|
+
- **Errors in OpenAI's shape and status.** For example, a plan usage limit becomes
|
|
60
|
+
a 429 `RateLimitError`, so normal client backoff works.
|
|
61
|
+
|
|
62
|
+
> [!IMPORTANT]
|
|
63
|
+
> **Personal, local use only.** This is an independent open-source project, not
|
|
64
|
+
> affiliated with or endorsed by OpenAI. It lets *you* use *your own* ChatGPT plan
|
|
65
|
+
> from tools on your own computer. Don't host it for others, expose it publicly,
|
|
66
|
+
> or share access to your plan. OpenAI's token-sharing docs cover open-source and
|
|
67
|
+
> locally hosted apps; paid or remotely hosted products need OpenAI's approval
|
|
68
|
+
> through its interest form (see the [token-sharing docs](https://developers.openai.com/siwc/token-sharing-open-source)).
|
|
69
|
+
> Current limits are listed under [preview limitations](https://developers.openai.com/siwc/token-sharing-open-source/preview-limitations).
|
|
70
|
+
> Sign in with ChatGPT token sharing is a preview, and the ChatGPT backend this
|
|
71
|
+
> proxy talks to can change or break without notice. Usage counts against your plan.
|
|
72
|
+
|
|
73
|
+
## Quick start
|
|
74
|
+
|
|
75
|
+
Install the `siwc-proxy` command with [uv](https://docs.astral.sh/uv/).
|
|
76
|
+
uv gets a suitable Python (3.12+) for it if you don't have one:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
uv tool install siwc-proxy
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Start the proxy:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
siwc-proxy serve
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
If you are not signed in yet, `serve` prints a sign-in link. Open it in a browser
|
|
89
|
+
**on the same computer**, or run this in a second terminal:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
siwc-proxy login
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
On a remote server or any machine without a browser, run `siwc-proxy login --remote`
|
|
96
|
+
there instead. Open the printed link on your own computer and sign in. Then copy the
|
|
97
|
+
`http://127.0.0.1:...` address the browser ends up on (the page fails to load;
|
|
98
|
+
that is expected) and paste it back into the terminal.
|
|
99
|
+
|
|
100
|
+
Then point any OpenAI client at the proxy:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from openai import OpenAI
|
|
104
|
+
|
|
105
|
+
client = OpenAI(base_url="http://127.0.0.1:8787/v1", api_key="<local key>")
|
|
106
|
+
model = client.models.list().data[0].id
|
|
107
|
+
print(client.responses.create(model=model, input="Say hello.").output_text)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The local key is generated on first run and shown by:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
siwc-proxy status --show-key
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Or choose your own key with `SIWC_PROXY_API_KEY`. The key only protects the
|
|
117
|
+
local proxy; it is never sent to OpenAI.
|
|
118
|
+
|
|
119
|
+
### More ways to run it
|
|
120
|
+
|
|
121
|
+
- **Without installing:** `uvx siwc-proxy serve`. Put `uvx` in front of every
|
|
122
|
+
`siwc-proxy` command in this README.
|
|
123
|
+
- **Upgrade:** `uv tool upgrade siwc-proxy`.
|
|
124
|
+
- **Not on your `PATH` after `uv tool install`?** Run `uv tool update-shell` and
|
|
125
|
+
open a new terminal.
|
|
126
|
+
- **pip or pipx:** `pipx install siwc-proxy`, or `pip install siwc-proxy` in a virtual
|
|
127
|
+
environment. `python -m siwc_proxy` works like the `siwc-proxy` command.
|
|
128
|
+
- **One-file scripts with uv:** declare `openai` inline and run the file with `uv run hello.py`:
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
# /// script
|
|
132
|
+
# dependencies = ["openai"]
|
|
133
|
+
# ///
|
|
134
|
+
import os
|
|
135
|
+
from openai import OpenAI
|
|
136
|
+
|
|
137
|
+
client = OpenAI(base_url="http://127.0.0.1:8787/v1", api_key=os.environ["SIWC_PROXY_API_KEY"])
|
|
138
|
+
model = client.models.list().data[0].id
|
|
139
|
+
print(client.responses.create(model=model, input="Say hello.").output_text)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Commands
|
|
143
|
+
|
|
144
|
+
| Command | What it does |
|
|
145
|
+
| --- | --- |
|
|
146
|
+
| `siwc-proxy serve` | Run the proxy (default `127.0.0.1:8787`). Starts even when signed out. |
|
|
147
|
+
| `siwc-proxy login [--consent] [--remote]` | Sign in or re-authorize. Uses the running proxy if there is one; otherwise hosts the callback on a temporary port. `--remote` signs in with a browser on another computer by pasting back the address it lands on. `--consent` shows ChatGPT's permission screen again, in case you didn't allow plan usage before. |
|
|
148
|
+
| `siwc-proxy logout [--forget]` | Revoke and clear tokens. `--forget` also drops the registration (needed to switch accounts). |
|
|
149
|
+
| `siwc-proxy status [--json] [--show-key]` | Account, state, scopes, expiry. Never prints tokens. |
|
|
150
|
+
| `siwc-proxy token` | Print a fresh access token to stdout, for scripts. |
|
|
151
|
+
|
|
152
|
+
Every command accepts `--data-dir`, before or after the command name (or `SIWC_PROXY_DATA_DIR`),
|
|
153
|
+
to choose a credential directory.
|
|
154
|
+
Use one directory per account.
|
|
155
|
+
|
|
156
|
+
`serve --host` can bind a non-loopback interface for Docker or LAN use, but the
|
|
157
|
+
proxy speaks plain HTTP: the key and your prompts then cross the network
|
|
158
|
+
unencrypted. See the warning in [docs/proxy.md](https://github.com/d-nyamkhuu/siwc-proxy/blob/main/docs/proxy.md#docker-localhost-and-lan-access).
|
|
159
|
+
|
|
160
|
+
See **[docs/proxy.md](https://github.com/d-nyamkhuu/siwc-proxy/blob/main/docs/proxy.md)** for configuration, routes, the Chat
|
|
161
|
+
translation rules, error mapping, and Docker/LAN access.
|
|
162
|
+
|
|
163
|
+
## Development
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
git clone https://github.com/d-nyamkhuu/siwc-proxy.git
|
|
167
|
+
cd siwc-proxy
|
|
168
|
+
uv sync
|
|
169
|
+
uv run pytest -q
|
|
170
|
+
uv run siwc-proxy serve # run the checkout
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The tests run the real `openai` SDK against the app. OpenAI's OAuth and API are
|
|
174
|
+
mocked: real RSA-signed ID tokens, rotating refresh tokens, and multi-process refresh races.
|
|
175
|
+
No live account is used.
|
|
176
|
+
|
|
177
|
+
### Releasing
|
|
178
|
+
|
|
179
|
+
The version lives only in `pyproject.toml`. Bump it, then push a matching tag:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
uv version --bump minor # or patch / major
|
|
183
|
+
git commit -am "Release $(uv version --short)"
|
|
184
|
+
git tag "v$(uv version --short)"
|
|
185
|
+
git push origin main --tags
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The [release workflow](https://github.com/d-nyamkhuu/siwc-proxy/blob/main/.github/workflows/release.yml) runs the tests, builds the
|
|
189
|
+
wheel and sdist, smoke-tests both, and publishes to PyPI with
|
|
190
|
+
[Trusted Publishing](https://docs.pypi.org/trusted-publishers/), so no API token is stored.
|
|
191
|
+
Before the first release, add a pending publisher on PyPI for project `siwc-proxy`,
|
|
192
|
+
repository `d-nyamkhuu/siwc-proxy`, workflow `release.yml`, environment `pypi`.
|
|
193
|
+
|
|
194
|
+
## References
|
|
195
|
+
|
|
196
|
+
- [Token sharing for open-source and locally hosted apps](https://developers.openai.com/siwc/token-sharing-open-source)
|
|
197
|
+
- [Registration and sign-in](https://developers.openai.com/siwc/token-sharing-open-source/sign-in)
|
|
198
|
+
- [Profiles and sessions](https://developers.openai.com/siwc/token-sharing-open-source/profiles-and-sessions)
|
|
199
|
+
- [Token reference](https://developers.openai.com/siwc/token-sharing-open-source/token-reference)
|
|
200
|
+
- [Models and inference](https://developers.openai.com/siwc/token-sharing-open-source/models-and-inference)
|
|
201
|
+
- [Preview limitations](https://developers.openai.com/siwc/token-sharing-open-source/preview-limitations)
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# siwc-proxy
|
|
2
|
+
|
|
3
|
+
A local **OpenAI-compatible API** backed by
|
|
4
|
+
[Sign in with ChatGPT token sharing](https://developers.openai.com/siwc/token-sharing-open-source).
|
|
5
|
+
Sign in once; the proxy keeps the ChatGPT plan credential fresh and serves
|
|
6
|
+
`/v1/chat/completions`, `/v1/responses`, and `/v1/models` to any OpenAI SDK,
|
|
7
|
+
framework, or tool that lets you set a base URL.
|
|
8
|
+
|
|
9
|
+
- **Sign-in and re-auth handled for you.** The proxy hosts its own OAuth callback,
|
|
10
|
+
registers its client on first use, and reuses that registration afterwards.
|
|
11
|
+
[Authlib](https://docs.authlib.org/) does PKCE, state, nonce, and ID-token checks.
|
|
12
|
+
- **Safe token refresh.** Rotating refresh tokens are refreshed once, even across
|
|
13
|
+
processes (file lock). Refreshed tokens are saved before they are checked, so
|
|
14
|
+
a failed check never loses the new refresh token.
|
|
15
|
+
A temporary outage never deletes credentials. An upstream 401 triggers one refresh and retry.
|
|
16
|
+
- **Works with ordinary client code.** `responses.create(input="hi")` without
|
|
17
|
+
`stream`/`store` works: the proxy streams upstream and aggregates the result.
|
|
18
|
+
Chat Completions is translated to Responses. Options that can't be honored,
|
|
19
|
+
such as `user` and `seed`, are dropped and listed in a response header.
|
|
20
|
+
- **Errors in OpenAI's shape and status.** For example, a plan usage limit becomes
|
|
21
|
+
a 429 `RateLimitError`, so normal client backoff works.
|
|
22
|
+
|
|
23
|
+
> [!IMPORTANT]
|
|
24
|
+
> **Personal, local use only.** This is an independent open-source project, not
|
|
25
|
+
> affiliated with or endorsed by OpenAI. It lets *you* use *your own* ChatGPT plan
|
|
26
|
+
> from tools on your own computer. Don't host it for others, expose it publicly,
|
|
27
|
+
> or share access to your plan. OpenAI's token-sharing docs cover open-source and
|
|
28
|
+
> locally hosted apps; paid or remotely hosted products need OpenAI's approval
|
|
29
|
+
> through its interest form (see the [token-sharing docs](https://developers.openai.com/siwc/token-sharing-open-source)).
|
|
30
|
+
> Current limits are listed under [preview limitations](https://developers.openai.com/siwc/token-sharing-open-source/preview-limitations).
|
|
31
|
+
> Sign in with ChatGPT token sharing is a preview, and the ChatGPT backend this
|
|
32
|
+
> proxy talks to can change or break without notice. Usage counts against your plan.
|
|
33
|
+
|
|
34
|
+
## Quick start
|
|
35
|
+
|
|
36
|
+
Install the `siwc-proxy` command with [uv](https://docs.astral.sh/uv/).
|
|
37
|
+
uv gets a suitable Python (3.12+) for it if you don't have one:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
uv tool install siwc-proxy
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Start the proxy:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
siwc-proxy serve
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
If you are not signed in yet, `serve` prints a sign-in link. Open it in a browser
|
|
50
|
+
**on the same computer**, or run this in a second terminal:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
siwc-proxy login
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
On a remote server or any machine without a browser, run `siwc-proxy login --remote`
|
|
57
|
+
there instead. Open the printed link on your own computer and sign in. Then copy the
|
|
58
|
+
`http://127.0.0.1:...` address the browser ends up on (the page fails to load;
|
|
59
|
+
that is expected) and paste it back into the terminal.
|
|
60
|
+
|
|
61
|
+
Then point any OpenAI client at the proxy:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from openai import OpenAI
|
|
65
|
+
|
|
66
|
+
client = OpenAI(base_url="http://127.0.0.1:8787/v1", api_key="<local key>")
|
|
67
|
+
model = client.models.list().data[0].id
|
|
68
|
+
print(client.responses.create(model=model, input="Say hello.").output_text)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The local key is generated on first run and shown by:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
siwc-proxy status --show-key
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Or choose your own key with `SIWC_PROXY_API_KEY`. The key only protects the
|
|
78
|
+
local proxy; it is never sent to OpenAI.
|
|
79
|
+
|
|
80
|
+
### More ways to run it
|
|
81
|
+
|
|
82
|
+
- **Without installing:** `uvx siwc-proxy serve`. Put `uvx` in front of every
|
|
83
|
+
`siwc-proxy` command in this README.
|
|
84
|
+
- **Upgrade:** `uv tool upgrade siwc-proxy`.
|
|
85
|
+
- **Not on your `PATH` after `uv tool install`?** Run `uv tool update-shell` and
|
|
86
|
+
open a new terminal.
|
|
87
|
+
- **pip or pipx:** `pipx install siwc-proxy`, or `pip install siwc-proxy` in a virtual
|
|
88
|
+
environment. `python -m siwc_proxy` works like the `siwc-proxy` command.
|
|
89
|
+
- **One-file scripts with uv:** declare `openai` inline and run the file with `uv run hello.py`:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
# /// script
|
|
93
|
+
# dependencies = ["openai"]
|
|
94
|
+
# ///
|
|
95
|
+
import os
|
|
96
|
+
from openai import OpenAI
|
|
97
|
+
|
|
98
|
+
client = OpenAI(base_url="http://127.0.0.1:8787/v1", api_key=os.environ["SIWC_PROXY_API_KEY"])
|
|
99
|
+
model = client.models.list().data[0].id
|
|
100
|
+
print(client.responses.create(model=model, input="Say hello.").output_text)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Commands
|
|
104
|
+
|
|
105
|
+
| Command | What it does |
|
|
106
|
+
| --- | --- |
|
|
107
|
+
| `siwc-proxy serve` | Run the proxy (default `127.0.0.1:8787`). Starts even when signed out. |
|
|
108
|
+
| `siwc-proxy login [--consent] [--remote]` | Sign in or re-authorize. Uses the running proxy if there is one; otherwise hosts the callback on a temporary port. `--remote` signs in with a browser on another computer by pasting back the address it lands on. `--consent` shows ChatGPT's permission screen again, in case you didn't allow plan usage before. |
|
|
109
|
+
| `siwc-proxy logout [--forget]` | Revoke and clear tokens. `--forget` also drops the registration (needed to switch accounts). |
|
|
110
|
+
| `siwc-proxy status [--json] [--show-key]` | Account, state, scopes, expiry. Never prints tokens. |
|
|
111
|
+
| `siwc-proxy token` | Print a fresh access token to stdout, for scripts. |
|
|
112
|
+
|
|
113
|
+
Every command accepts `--data-dir`, before or after the command name (or `SIWC_PROXY_DATA_DIR`),
|
|
114
|
+
to choose a credential directory.
|
|
115
|
+
Use one directory per account.
|
|
116
|
+
|
|
117
|
+
`serve --host` can bind a non-loopback interface for Docker or LAN use, but the
|
|
118
|
+
proxy speaks plain HTTP: the key and your prompts then cross the network
|
|
119
|
+
unencrypted. See the warning in [docs/proxy.md](https://github.com/d-nyamkhuu/siwc-proxy/blob/main/docs/proxy.md#docker-localhost-and-lan-access).
|
|
120
|
+
|
|
121
|
+
See **[docs/proxy.md](https://github.com/d-nyamkhuu/siwc-proxy/blob/main/docs/proxy.md)** for configuration, routes, the Chat
|
|
122
|
+
translation rules, error mapping, and Docker/LAN access.
|
|
123
|
+
|
|
124
|
+
## Development
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
git clone https://github.com/d-nyamkhuu/siwc-proxy.git
|
|
128
|
+
cd siwc-proxy
|
|
129
|
+
uv sync
|
|
130
|
+
uv run pytest -q
|
|
131
|
+
uv run siwc-proxy serve # run the checkout
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The tests run the real `openai` SDK against the app. OpenAI's OAuth and API are
|
|
135
|
+
mocked: real RSA-signed ID tokens, rotating refresh tokens, and multi-process refresh races.
|
|
136
|
+
No live account is used.
|
|
137
|
+
|
|
138
|
+
### Releasing
|
|
139
|
+
|
|
140
|
+
The version lives only in `pyproject.toml`. Bump it, then push a matching tag:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
uv version --bump minor # or patch / major
|
|
144
|
+
git commit -am "Release $(uv version --short)"
|
|
145
|
+
git tag "v$(uv version --short)"
|
|
146
|
+
git push origin main --tags
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The [release workflow](https://github.com/d-nyamkhuu/siwc-proxy/blob/main/.github/workflows/release.yml) runs the tests, builds the
|
|
150
|
+
wheel and sdist, smoke-tests both, and publishes to PyPI with
|
|
151
|
+
[Trusted Publishing](https://docs.pypi.org/trusted-publishers/), so no API token is stored.
|
|
152
|
+
Before the first release, add a pending publisher on PyPI for project `siwc-proxy`,
|
|
153
|
+
repository `d-nyamkhuu/siwc-proxy`, workflow `release.yml`, environment `pypi`.
|
|
154
|
+
|
|
155
|
+
## References
|
|
156
|
+
|
|
157
|
+
- [Token sharing for open-source and locally hosted apps](https://developers.openai.com/siwc/token-sharing-open-source)
|
|
158
|
+
- [Registration and sign-in](https://developers.openai.com/siwc/token-sharing-open-source/sign-in)
|
|
159
|
+
- [Profiles and sessions](https://developers.openai.com/siwc/token-sharing-open-source/profiles-and-sessions)
|
|
160
|
+
- [Token reference](https://developers.openai.com/siwc/token-sharing-open-source/token-reference)
|
|
161
|
+
- [Models and inference](https://developers.openai.com/siwc/token-sharing-open-source/models-and-inference)
|
|
162
|
+
- [Preview limitations](https://developers.openai.com/siwc/token-sharing-open-source/preview-limitations)
|