codex-api-proxy 0.1.2__py3-none-any.whl → 0.1.4__py3-none-any.whl
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.
- codex_api_proxy/__init__.py +1 -1
- codex_api_proxy/chat_completions.py +64 -1
- codex_api_proxy/config.py +9 -0
- {codex_api_proxy-0.1.2.dist-info → codex_api_proxy-0.1.4.dist-info}/METADATA +42 -1
- codex_api_proxy-0.1.4.dist-info/RECORD +13 -0
- codex_api_proxy-0.1.2.dist-info/RECORD +0 -13
- {codex_api_proxy-0.1.2.dist-info → codex_api_proxy-0.1.4.dist-info}/WHEEL +0 -0
- {codex_api_proxy-0.1.2.dist-info → codex_api_proxy-0.1.4.dist-info}/entry_points.txt +0 -0
- {codex_api_proxy-0.1.2.dist-info → codex_api_proxy-0.1.4.dist-info}/top_level.txt +0 -0
codex_api_proxy/__init__.py
CHANGED
|
@@ -46,7 +46,7 @@ def chat_to_responses_request(
|
|
|
46
46
|
{
|
|
47
47
|
"type": "message",
|
|
48
48
|
"role": "user",
|
|
49
|
-
"content":
|
|
49
|
+
"content": message_content_to_response_content(message.get("content", "")),
|
|
50
50
|
}
|
|
51
51
|
)
|
|
52
52
|
continue
|
|
@@ -147,6 +147,69 @@ def message_content_to_text(content: object) -> str:
|
|
|
147
147
|
return "".join(parts)
|
|
148
148
|
|
|
149
149
|
|
|
150
|
+
def message_content_to_response_content(content: object) -> list[dict[str, str]]:
|
|
151
|
+
if isinstance(content, str):
|
|
152
|
+
return [{"type": "input_text", "text": content}]
|
|
153
|
+
if not isinstance(content, list):
|
|
154
|
+
return [{"type": "input_text", "text": ""}]
|
|
155
|
+
|
|
156
|
+
parts: list[dict[str, str]] = []
|
|
157
|
+
for item in content:
|
|
158
|
+
if isinstance(item, str):
|
|
159
|
+
parts.append({"type": "input_text", "text": item})
|
|
160
|
+
continue
|
|
161
|
+
if not isinstance(item, dict):
|
|
162
|
+
continue
|
|
163
|
+
|
|
164
|
+
part_type = item.get("type")
|
|
165
|
+
text = item.get("text")
|
|
166
|
+
if part_type in {"text", "input_text"} and isinstance(text, str):
|
|
167
|
+
parts.append({"type": "input_text", "text": text})
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
image_part = _image_content_part(item)
|
|
171
|
+
if image_part:
|
|
172
|
+
parts.append(image_part)
|
|
173
|
+
|
|
174
|
+
return parts or [{"type": "input_text", "text": ""}]
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _image_content_part(item: dict) -> dict[str, str] | None:
|
|
178
|
+
part_type = item.get("type")
|
|
179
|
+
if part_type == "image_url":
|
|
180
|
+
image_url = item.get("image_url")
|
|
181
|
+
url: object
|
|
182
|
+
detail: object = item.get("detail")
|
|
183
|
+
if isinstance(image_url, dict):
|
|
184
|
+
url = image_url.get("url")
|
|
185
|
+
detail = image_url.get("detail", detail)
|
|
186
|
+
else:
|
|
187
|
+
url = image_url
|
|
188
|
+
if not isinstance(url, str):
|
|
189
|
+
return None
|
|
190
|
+
part = {"type": "input_image", "image_url": url}
|
|
191
|
+
if isinstance(detail, str):
|
|
192
|
+
part["detail"] = detail
|
|
193
|
+
return part
|
|
194
|
+
|
|
195
|
+
if part_type == "input_image":
|
|
196
|
+
part = {"type": "input_image"}
|
|
197
|
+
image_url = item.get("image_url")
|
|
198
|
+
file_id = item.get("file_id")
|
|
199
|
+
detail = item.get("detail")
|
|
200
|
+
if isinstance(image_url, str):
|
|
201
|
+
part["image_url"] = image_url
|
|
202
|
+
if isinstance(file_id, str):
|
|
203
|
+
part["file_id"] = file_id
|
|
204
|
+
if isinstance(detail, str):
|
|
205
|
+
part["detail"] = detail
|
|
206
|
+
if "image_url" not in part and "file_id" not in part:
|
|
207
|
+
return None
|
|
208
|
+
return part
|
|
209
|
+
|
|
210
|
+
return None
|
|
211
|
+
|
|
212
|
+
|
|
150
213
|
def extract_output_text(response: dict) -> str:
|
|
151
214
|
chunks: list[str] = []
|
|
152
215
|
for item in response.get("output", []):
|
codex_api_proxy/config.py
CHANGED
|
@@ -76,6 +76,14 @@ def upstream_https_proxy(explicit_proxy: str | None = None) -> str | None:
|
|
|
76
76
|
return DEFAULT_HTTPS_PROXY
|
|
77
77
|
|
|
78
78
|
|
|
79
|
+
def upstream_ssl_verify() -> bool | str:
|
|
80
|
+
for key in ("CODEX_API_PROXY_CA_BUNDLE", "REQUESTS_CA_BUNDLE", "SSL_CERT_FILE"):
|
|
81
|
+
value = os.environ.get(key, "").strip()
|
|
82
|
+
if value:
|
|
83
|
+
return value
|
|
84
|
+
return True
|
|
85
|
+
|
|
86
|
+
|
|
79
87
|
def upstream_originator() -> str:
|
|
80
88
|
override = os.environ.get("CODEX_INTERNAL_ORIGINATOR_OVERRIDE", "").strip()
|
|
81
89
|
return override or DEFAULT_ORIGINATOR
|
|
@@ -95,6 +103,7 @@ def upstream_client(explicit_proxy: str | None = None) -> httpx.AsyncClient:
|
|
|
95
103
|
return httpx.AsyncClient(
|
|
96
104
|
timeout=DEFAULT_TIMEOUT,
|
|
97
105
|
proxy=upstream_https_proxy(explicit_proxy),
|
|
106
|
+
verify=upstream_ssl_verify(),
|
|
98
107
|
trust_env=False,
|
|
99
108
|
)
|
|
100
109
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codex-api-proxy
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Local OpenAI-compatible HTTP proxy backed by Codex/OpenAI credentials
|
|
5
5
|
Author: codex-api-proxy contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -102,6 +102,20 @@ You can also set the upstream proxy explicitly at startup:
|
|
|
102
102
|
codex-api-proxy start --proxy=http://127.0.0.1:8118
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
+
If the upstream connection passes through a corporate TLS proxy or another endpoint
|
|
106
|
+
using a private/self-signed certificate chain, point the proxy at a CA bundle that
|
|
107
|
+
trusts that chain:
|
|
108
|
+
|
|
109
|
+
- `CODEX_API_PROXY_CA_BUNDLE`
|
|
110
|
+
- `REQUESTS_CA_BUNDLE`
|
|
111
|
+
- `SSL_CERT_FILE`
|
|
112
|
+
|
|
113
|
+
For example:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
CODEX_API_PROXY_CA_BUNDLE=/path/to/internal-ca-bundle.pem codex-api-proxy start
|
|
117
|
+
```
|
|
118
|
+
|
|
105
119
|
## Run
|
|
106
120
|
|
|
107
121
|
Start in the background:
|
|
@@ -164,6 +178,7 @@ Environment variables for the local server:
|
|
|
164
178
|
- `CODEX_PROXY_PORT`
|
|
165
179
|
- `CODEX_PROXY_API_KEY`
|
|
166
180
|
- `CODEX_API_PROXY_HTTPS_PROXY`
|
|
181
|
+
- `CODEX_API_PROXY_CA_BUNDLE`
|
|
167
182
|
- `CODEX_PROXY_LOG_LEVEL`
|
|
168
183
|
|
|
169
184
|
Token refresh compatibility variables:
|
|
@@ -209,6 +224,32 @@ curl -sS http://127.0.0.1:8765/v1/responses \
|
|
|
209
224
|
-d '{"model":"gpt-5.5","input":"Reply with exactly: pong"}'
|
|
210
225
|
```
|
|
211
226
|
|
|
227
|
+
Image input through Chat Completions:
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
BASE64_IMAGE=$(base64 < image.jpg)
|
|
231
|
+
curl -sS http://127.0.0.1:8765/v1/chat/completions \
|
|
232
|
+
-H 'Content-Type: application/json' \
|
|
233
|
+
-d '{
|
|
234
|
+
"model": "gpt-5.5",
|
|
235
|
+
"messages": [{
|
|
236
|
+
"role": "user",
|
|
237
|
+
"content": [
|
|
238
|
+
{"type": "text", "text": "What is in this image?"},
|
|
239
|
+
{
|
|
240
|
+
"type": "image_url",
|
|
241
|
+
"image_url": {
|
|
242
|
+
"url": "data:image/jpeg;base64,'"$BASE64_IMAGE"'",
|
|
243
|
+
"detail": "high"
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
]
|
|
247
|
+
}]
|
|
248
|
+
}'
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
When using ChatGPT Codex credentials, Chat Completions image parts are converted to Responses API `input_image` parts. `/v1/responses` requests are passed through unchanged.
|
|
252
|
+
|
|
212
253
|
When `--api-key` is configured:
|
|
213
254
|
|
|
214
255
|
```bash
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
codex_api_proxy/__init__.py,sha256=kST1dtci895ogqNSin9iq_TpqSfvECZ13FXqGJJOdZY,94
|
|
2
|
+
codex_api_proxy/auth.py,sha256=TG3dyhMFwRHgUyPyWMe-zbuuhIRAz_mva0oH2J_OfQg,10894
|
|
3
|
+
codex_api_proxy/chat_completions.py,sha256=NoS2bRwCXUmydWF-KobT5OXk-nMJSjHknje4XMk9q18,11057
|
|
4
|
+
codex_api_proxy/cli.py,sha256=ljaXci-vjfhNNxJi9ej7rB-iijZViHriA-xW19PW-D4,15806
|
|
5
|
+
codex_api_proxy/config.py,sha256=Uy_XIzk22ChBLwHPY8mf3frf6H1p2XKkozjqniqSkJc,3775
|
|
6
|
+
codex_api_proxy/main.py,sha256=9xBRqFV9TsLhAALhNBvBU1ci5ulCCs29qvEMmJwudts,23224
|
|
7
|
+
codex_api_proxy/models.py,sha256=x8JH44SBC3nC4F_OeZMLnNM3q2qWqBFxHQJOmZ-7adk,3411
|
|
8
|
+
codex_api_proxy/sse_utils.py,sha256=Tkn63Edv9l8rHjZPtx65U9PDkVV4cgq7hQS_FYwdIoc,1064
|
|
9
|
+
codex_api_proxy-0.1.4.dist-info/METADATA,sha256=1s13zqbAqw_bohRn3-G4ak51HSneemqgZrpiU7zaOyk,7650
|
|
10
|
+
codex_api_proxy-0.1.4.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
11
|
+
codex_api_proxy-0.1.4.dist-info/entry_points.txt,sha256=a28sZIZgr1ImRSTMZ2ba8uDSUTJcgLtr1CgVLDdTgjE,61
|
|
12
|
+
codex_api_proxy-0.1.4.dist-info/top_level.txt,sha256=hksltGPYaEc8y1_UkFJjXOdNQRLhHIR8-8NLTpLMqrw,16
|
|
13
|
+
codex_api_proxy-0.1.4.dist-info/RECORD,,
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
codex_api_proxy/__init__.py,sha256=PqpkJejAPDKHipW2iDcxHullWyc9padUv2u52f3AC3A,94
|
|
2
|
-
codex_api_proxy/auth.py,sha256=TG3dyhMFwRHgUyPyWMe-zbuuhIRAz_mva0oH2J_OfQg,10894
|
|
3
|
-
codex_api_proxy/chat_completions.py,sha256=AykPAzkyVOzBcT1prepjzvACO4-Njk-LJF3G_oWvQ5U,8971
|
|
4
|
-
codex_api_proxy/cli.py,sha256=ljaXci-vjfhNNxJi9ej7rB-iijZViHriA-xW19PW-D4,15806
|
|
5
|
-
codex_api_proxy/config.py,sha256=4fVHv8zsg8brpioOA9w_YZXCpuiFGizYeatlya4VLc4,3502
|
|
6
|
-
codex_api_proxy/main.py,sha256=9xBRqFV9TsLhAALhNBvBU1ci5ulCCs29qvEMmJwudts,23224
|
|
7
|
-
codex_api_proxy/models.py,sha256=x8JH44SBC3nC4F_OeZMLnNM3q2qWqBFxHQJOmZ-7adk,3411
|
|
8
|
-
codex_api_proxy/sse_utils.py,sha256=Tkn63Edv9l8rHjZPtx65U9PDkVV4cgq7hQS_FYwdIoc,1064
|
|
9
|
-
codex_api_proxy-0.1.2.dist-info/METADATA,sha256=y29LnfG7dDck0seQ_si0wXOZIBqgHtj0knzntOgG0lM,6547
|
|
10
|
-
codex_api_proxy-0.1.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
11
|
-
codex_api_proxy-0.1.2.dist-info/entry_points.txt,sha256=a28sZIZgr1ImRSTMZ2ba8uDSUTJcgLtr1CgVLDdTgjE,61
|
|
12
|
-
codex_api_proxy-0.1.2.dist-info/top_level.txt,sha256=hksltGPYaEc8y1_UkFJjXOdNQRLhHIR8-8NLTpLMqrw,16
|
|
13
|
-
codex_api_proxy-0.1.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|