codex-ai 0.2.4__tar.gz → 0.2.5__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.
- {codex_ai-0.2.4 → codex_ai-0.2.5}/CHANGELOG.md +18 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/PKG-INFO +46 -8
- {codex_ai-0.2.4 → codex_ai-0.2.5}/README.md +42 -4
- {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/en/architecture/providers/README.md +18 -4
- {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/en/architecture/providers/data_flow.md +35 -2
- {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/ru/architecture/providers/README.md +18 -4
- {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/ru/architecture/providers/data_flow.md +35 -2
- {codex_ai-0.2.4 → codex_ai-0.2.5}/pyproject.toml +1 -1
- {codex_ai-0.2.4 → codex_ai-0.2.5}/src/codex_ai/__init__.py +4 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/src/codex_ai/core/__init__.py +4 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/src/codex_ai/core/dispatcher.py +3 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/src/codex_ai/core/protocol.py +22 -1
- {codex_ai-0.2.4 → codex_ai-0.2.5}/src/codex_ai/providers/__init__.py +1 -1
- {codex_ai-0.2.4 → codex_ai-0.2.5}/src/codex_ai/providers/gemini.py +41 -5
- codex_ai-0.2.5/src/codex_ai/providers/openai.py +265 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/integration/test_providers_integration.py +3 -3
- {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/unit/core/test_dispatcher.py +12 -3
- {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/unit/core/test_protocol.py +9 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/unit/providers/test_gemini_provider.py +29 -0
- codex_ai-0.2.5/tests/unit/providers/test_openai_provider.py +272 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/unit/test_public_api.py +1 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/uv.lock +103 -98
- codex_ai-0.2.4/src/codex_ai/providers/openai.py +0 -138
- codex_ai-0.2.4/tests/unit/providers/test_openai_provider.py +0 -283
- {codex_ai-0.2.4 → codex_ai-0.2.5}/.github/workflows/ci.yml +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/.github/workflows/docs.yml +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/.github/workflows/publish.yml +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/.gitignore +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/.nojekyll +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/.pre-commit-config.yaml +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/.python-version +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/.secrets.baseline +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/LICENSE +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/changelog.md +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/en/api/core/dispatcher.md +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/en/api/core/exceptions.md +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/en/api/core/protocol.md +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/en/api/core/router.md +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/en/api/core/sync.md +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/en/api/index.md +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/en/api/providers/gemini.md +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/en/api/providers/openai.md +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/en/architecture/core/README.md +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/en/architecture/core/data_flow.md +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/index.md +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/ru/architecture/core/README.md +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/ru/architecture/core/data_flow.md +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/stylesheets/extra.css +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/mkdocs.yml +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/src/codex_ai/core/exceptions.py +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/src/codex_ai/core/router.py +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/src/codex_ai/core/sync.py +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/src/codex_ai/py.typed +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/conftest.py +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/integration/__init__.py +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/integration/conftest.py +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/unit/__init__.py +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/unit/conftest.py +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/unit/core/__init__.py +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/unit/core/test_exceptions.py +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/unit/core/test_router.py +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/unit/core/test_sync.py +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/unit/providers/__init__.py +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/tools/__init__.py +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/tools/dev/README.md +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/tools/dev/__init__.py +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/tools/dev/check.py +0 -0
- {codex_ai-0.2.4 → codex_ai-0.2.5}/tools/dev/generate_project_tree.py +0 -0
|
@@ -4,6 +4,24 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.2.5] - 2026-07-19
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Added `ImageInput` and optional `input_images` support to Gemini `generate_image_bytes()` for image reference/edit workflows.
|
|
11
|
+
- Added OpenAI structured output generation through `generate_json()` with native Pydantic parsing.
|
|
12
|
+
- Added typed OpenAI Responses API text streaming through `stream_text()`.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- Replaced the OpenAI Chat Completions implementation with the Responses API.
|
|
16
|
+
- Updated the OpenAI SDK requirement to `openai>=2.0,<3.0`.
|
|
17
|
+
- Changed the OpenAI default model to `gpt-5.6-luna` with `reasoning={"effort": "none"}` and `store=False` defaults.
|
|
18
|
+
|
|
19
|
+
## [0.2.4] - 2026-05-21
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- Widened the supported `codex-core` range to include the `0.4.x` series.
|
|
23
|
+
- Clarified the Gemini-first provider focus in the package documentation.
|
|
24
|
+
|
|
7
25
|
## [0.2.3] - 2026-05-17
|
|
8
26
|
|
|
9
27
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codex-ai
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: Gemini-first and OpenAI provider helpers for Codex
|
|
5
5
|
Project-URL: Homepage, https://github.com/codexdlc/codex-ai
|
|
6
6
|
Project-URL: Documentation, https://codexdlc.github.io/codex-ai/
|
|
@@ -20,13 +20,13 @@ Requires-Dist: codex-core<0.5.0,>=0.2.2
|
|
|
20
20
|
Requires-Dist: pydantic<3.0,>=2.0
|
|
21
21
|
Provides-Extra: all
|
|
22
22
|
Requires-Dist: google-genai==2.3.0; extra == 'all'
|
|
23
|
-
Requires-Dist: openai<
|
|
23
|
+
Requires-Dist: openai<3.0,>=2.0; extra == 'all'
|
|
24
24
|
Provides-Extra: dev
|
|
25
25
|
Requires-Dist: bandit>=1.7; extra == 'dev'
|
|
26
26
|
Requires-Dist: detect-secrets>=1.5; extra == 'dev'
|
|
27
27
|
Requires-Dist: google-genai==2.3.0; extra == 'dev'
|
|
28
28
|
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
29
|
-
Requires-Dist: openai<
|
|
29
|
+
Requires-Dist: openai<3.0,>=2.0; extra == 'dev'
|
|
30
30
|
Requires-Dist: pip-audit>=2.7; extra == 'dev'
|
|
31
31
|
Requires-Dist: pre-commit>=3.0; extra == 'dev'
|
|
32
32
|
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
@@ -42,7 +42,7 @@ Requires-Dist: mkdocstrings[python]>=0.24; extra == 'docs'
|
|
|
42
42
|
Provides-Extra: gemini
|
|
43
43
|
Requires-Dist: google-genai==2.3.0; extra == 'gemini'
|
|
44
44
|
Provides-Extra: openai
|
|
45
|
-
Requires-Dist: openai<
|
|
45
|
+
Requires-Dist: openai<3.0,>=2.0; extra == 'openai'
|
|
46
46
|
Description-Content-Type: text/markdown
|
|
47
47
|
|
|
48
48
|
# codex-ai <!-- Type: LANDING -->
|
|
@@ -52,7 +52,7 @@ Description-Content-Type: text/markdown
|
|
|
52
52
|
[](https://github.com/codexdlc/codex-ai/actions/workflows/ci.yml)
|
|
53
53
|
[](https://github.com/codexdlc/codex-ai/blob/main/LICENSE)
|
|
54
54
|
|
|
55
|
-
Gemini-first API helpers for the Codex ecosystem. The active surface is direct Gemini text, JSON, Gemini image, and Imagen generation. OpenAI
|
|
55
|
+
Gemini-first API helpers for the Codex ecosystem. The active surface is direct Gemini text, JSON, Gemini image, and Imagen generation. OpenAI provides modern Responses API text, structured JSON, and streaming helpers. The router/dispatcher layer is kept for legacy text workflows.
|
|
56
56
|
|
|
57
57
|
## Install
|
|
58
58
|
|
|
@@ -68,9 +68,11 @@ Requires Python 3.12 or newer.
|
|
|
68
68
|
## Gemini Direct API
|
|
69
69
|
|
|
70
70
|
```python
|
|
71
|
+
from pathlib import Path
|
|
72
|
+
|
|
71
73
|
from pydantic import BaseModel
|
|
72
74
|
|
|
73
|
-
from codex_ai import GeminiProvider
|
|
75
|
+
from codex_ai import GeminiProvider, ImageInput
|
|
74
76
|
|
|
75
77
|
|
|
76
78
|
class LootItem(BaseModel):
|
|
@@ -89,6 +91,13 @@ image_bytes, content_type = await gemini.generate_image_bytes(
|
|
|
89
91
|
image_config={"aspect_ratio": "1:1", "image_size": "4K"},
|
|
90
92
|
)
|
|
91
93
|
|
|
94
|
+
source_image_bytes = Path("source.png").read_bytes()
|
|
95
|
+
edited_bytes, edited_content_type = await gemini.generate_image_bytes(
|
|
96
|
+
"Keep the composition, but redraw it as a watercolor map.",
|
|
97
|
+
response_mime_type="image/png",
|
|
98
|
+
input_images=[ImageInput(data=source_image_bytes, mime_type="image/png")],
|
|
99
|
+
)
|
|
100
|
+
|
|
92
101
|
imagen_bytes, imagen_content_type = await gemini.generate_imagen_bytes(
|
|
93
102
|
"A fantasy clan banner, game icon style.",
|
|
94
103
|
response_mime_type="image/jpeg",
|
|
@@ -101,10 +110,39 @@ imagen_bytes, imagen_content_type = await gemini.generate_imagen_bytes(
|
|
|
101
110
|
`response_mime_type` as a preferred/fallback MIME type. It does not pass image MIME values
|
|
102
111
|
to Gemini's text `response_mime_type` config field. Pass Gemini image controls such as
|
|
103
112
|
`aspect_ratio` and `image_size` with `image_config`; if a `4K` request is rejected,
|
|
104
|
-
the Gemini provider retries once with `2K`.
|
|
113
|
+
the Gemini provider retries once with `2K`. Pass `input_images` with `ImageInput`
|
|
114
|
+
items to provide reference/edit-source images for Gemini image models. Use `generate_imagen_bytes()` for
|
|
105
115
|
Imagen models; that path uses `generate_images` and passes the requested MIME as
|
|
106
116
|
`output_mime_type`.
|
|
107
117
|
|
|
118
|
+
## OpenAI Responses API
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from pydantic import BaseModel
|
|
122
|
+
|
|
123
|
+
from codex_ai import OpenAIProvider
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class LootItem(BaseModel):
|
|
127
|
+
name: str
|
|
128
|
+
power: int
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
openai = OpenAIProvider(api_key="sk-...")
|
|
132
|
+
|
|
133
|
+
text = await openai.generate_text("Write one short tavern rumor.")
|
|
134
|
+
loot = await openai.generate_json("Create one loot item.", schema=LootItem)
|
|
135
|
+
|
|
136
|
+
async for chunk in openai.stream_text("Tell a short story."):
|
|
137
|
+
print(chunk, end="")
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The OpenAI provider uses the Responses API and the `openai` 2.x SDK. Responses
|
|
141
|
+
are not stored by default. Its default `gpt-5.6-luna` model uses
|
|
142
|
+
`reasoning={"effort": "none"}` to retain a cost- and latency-sensitive role;
|
|
143
|
+
override the model and reasoning options per request when a workload needs more
|
|
144
|
+
capability.
|
|
145
|
+
|
|
108
146
|
## Legacy Text Router
|
|
109
147
|
|
|
110
148
|
```python
|
|
@@ -136,7 +174,7 @@ New Gemini integrations should call `generate_text()`, `generate_json()`,
|
|
|
136
174
|
| Module | Extra | Description |
|
|
137
175
|
| :--- | :--- | :--- |
|
|
138
176
|
| `codex_ai.providers.gemini` | `[gemini]` | Primary API: Gemini text, JSON, Gemini image, and Imagen generation via pinned `google-genai` |
|
|
139
|
-
| `codex_ai.providers.openai` | `[openai]` |
|
|
177
|
+
| `codex_ai.providers.openai` | `[openai]` | OpenAI Responses API text, structured JSON, and streaming adapter |
|
|
140
178
|
| `codex_ai.core` | - | Legacy text router/dispatcher contracts and shared provider exceptions |
|
|
141
179
|
|
|
142
180
|
## Development
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://github.com/codexdlc/codex-ai/actions/workflows/ci.yml)
|
|
6
6
|
[](https://github.com/codexdlc/codex-ai/blob/main/LICENSE)
|
|
7
7
|
|
|
8
|
-
Gemini-first API helpers for the Codex ecosystem. The active surface is direct Gemini text, JSON, Gemini image, and Imagen generation. OpenAI
|
|
8
|
+
Gemini-first API helpers for the Codex ecosystem. The active surface is direct Gemini text, JSON, Gemini image, and Imagen generation. OpenAI provides modern Responses API text, structured JSON, and streaming helpers. The router/dispatcher layer is kept for legacy text workflows.
|
|
9
9
|
|
|
10
10
|
## Install
|
|
11
11
|
|
|
@@ -21,9 +21,11 @@ Requires Python 3.12 or newer.
|
|
|
21
21
|
## Gemini Direct API
|
|
22
22
|
|
|
23
23
|
```python
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
24
26
|
from pydantic import BaseModel
|
|
25
27
|
|
|
26
|
-
from codex_ai import GeminiProvider
|
|
28
|
+
from codex_ai import GeminiProvider, ImageInput
|
|
27
29
|
|
|
28
30
|
|
|
29
31
|
class LootItem(BaseModel):
|
|
@@ -42,6 +44,13 @@ image_bytes, content_type = await gemini.generate_image_bytes(
|
|
|
42
44
|
image_config={"aspect_ratio": "1:1", "image_size": "4K"},
|
|
43
45
|
)
|
|
44
46
|
|
|
47
|
+
source_image_bytes = Path("source.png").read_bytes()
|
|
48
|
+
edited_bytes, edited_content_type = await gemini.generate_image_bytes(
|
|
49
|
+
"Keep the composition, but redraw it as a watercolor map.",
|
|
50
|
+
response_mime_type="image/png",
|
|
51
|
+
input_images=[ImageInput(data=source_image_bytes, mime_type="image/png")],
|
|
52
|
+
)
|
|
53
|
+
|
|
45
54
|
imagen_bytes, imagen_content_type = await gemini.generate_imagen_bytes(
|
|
46
55
|
"A fantasy clan banner, game icon style.",
|
|
47
56
|
response_mime_type="image/jpeg",
|
|
@@ -54,10 +63,39 @@ imagen_bytes, imagen_content_type = await gemini.generate_imagen_bytes(
|
|
|
54
63
|
`response_mime_type` as a preferred/fallback MIME type. It does not pass image MIME values
|
|
55
64
|
to Gemini's text `response_mime_type` config field. Pass Gemini image controls such as
|
|
56
65
|
`aspect_ratio` and `image_size` with `image_config`; if a `4K` request is rejected,
|
|
57
|
-
the Gemini provider retries once with `2K`.
|
|
66
|
+
the Gemini provider retries once with `2K`. Pass `input_images` with `ImageInput`
|
|
67
|
+
items to provide reference/edit-source images for Gemini image models. Use `generate_imagen_bytes()` for
|
|
58
68
|
Imagen models; that path uses `generate_images` and passes the requested MIME as
|
|
59
69
|
`output_mime_type`.
|
|
60
70
|
|
|
71
|
+
## OpenAI Responses API
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from pydantic import BaseModel
|
|
75
|
+
|
|
76
|
+
from codex_ai import OpenAIProvider
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class LootItem(BaseModel):
|
|
80
|
+
name: str
|
|
81
|
+
power: int
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
openai = OpenAIProvider(api_key="sk-...")
|
|
85
|
+
|
|
86
|
+
text = await openai.generate_text("Write one short tavern rumor.")
|
|
87
|
+
loot = await openai.generate_json("Create one loot item.", schema=LootItem)
|
|
88
|
+
|
|
89
|
+
async for chunk in openai.stream_text("Tell a short story."):
|
|
90
|
+
print(chunk, end="")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The OpenAI provider uses the Responses API and the `openai` 2.x SDK. Responses
|
|
94
|
+
are not stored by default. Its default `gpt-5.6-luna` model uses
|
|
95
|
+
`reasoning={"effort": "none"}` to retain a cost- and latency-sensitive role;
|
|
96
|
+
override the model and reasoning options per request when a workload needs more
|
|
97
|
+
capability.
|
|
98
|
+
|
|
61
99
|
## Legacy Text Router
|
|
62
100
|
|
|
63
101
|
```python
|
|
@@ -89,7 +127,7 @@ New Gemini integrations should call `generate_text()`, `generate_json()`,
|
|
|
89
127
|
| Module | Extra | Description |
|
|
90
128
|
| :--- | :--- | :--- |
|
|
91
129
|
| `codex_ai.providers.gemini` | `[gemini]` | Primary API: Gemini text, JSON, Gemini image, and Imagen generation via pinned `google-genai` |
|
|
92
|
-
| `codex_ai.providers.openai` | `[openai]` |
|
|
130
|
+
| `codex_ai.providers.openai` | `[openai]` | OpenAI Responses API text, structured JSON, and streaming adapter |
|
|
93
131
|
| `codex_ai.core` | - | Legacy text router/dispatcher contracts and shared provider exceptions |
|
|
94
132
|
|
|
95
133
|
## Development
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
`codex_ai.providers` contains concrete SDK adapters, not a broad interchangeable provider framework. Gemini is the product focus; OpenAI is
|
|
5
|
+
`codex_ai.providers` contains concrete SDK adapters, not a broad interchangeable provider framework. Gemini is the product focus; OpenAI is a separate modern Responses API adapter.
|
|
6
6
|
|
|
7
7
|
Gemini is the primary target and exposes direct methods for text, JSON, and image generation:
|
|
8
8
|
|
|
@@ -13,7 +13,14 @@ await gemini.generate_image_bytes(...)
|
|
|
13
13
|
await gemini.generate_imagen_bytes(...)
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
OpenAI
|
|
16
|
+
OpenAI exposes direct text, structured JSON, and typed streaming helpers:
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
await openai.generate_text(...)
|
|
20
|
+
await openai.generate_json(..., schema=MyModel)
|
|
21
|
+
async for chunk in openai.stream_text(...):
|
|
22
|
+
...
|
|
23
|
+
```
|
|
17
24
|
|
|
18
25
|
## Architecture
|
|
19
26
|
|
|
@@ -24,7 +31,9 @@ PromptResult/String
|
|
|
24
31
|
├── GeminiProvider.generate_json(...) -> dict | BaseModel
|
|
25
32
|
├── GeminiProvider.generate_image_bytes(...) -> tuple[bytes, str]
|
|
26
33
|
├── GeminiProvider.generate_imagen_bytes(...) -> tuple[bytes, str]
|
|
27
|
-
|
|
34
|
+
├── OpenAIProvider.generate_text(...) -> str
|
|
35
|
+
├── OpenAIProvider.generate_json(...) -> dict | BaseModel
|
|
36
|
+
└── OpenAIProvider.stream_text(...) -> AsyncIterator[str]
|
|
28
37
|
```
|
|
29
38
|
|
|
30
39
|
The legacy router pipeline remains available:
|
|
@@ -40,14 +49,19 @@ LLMRouter builder -> PromptResult -> LLMDispatcher.process() -> provider.answer(
|
|
|
40
49
|
| Component | Class | SDK | Default Model |
|
|
41
50
|
|-----------|-------|-----|---------------|
|
|
42
51
|
| `gemini.py` | `GeminiProvider` | `google-genai` | `gemini-2.5-flash-lite` |
|
|
43
|
-
| `openai.py` | `OpenAIProvider` | `openai` | `gpt-
|
|
52
|
+
| `openai.py` | `OpenAIProvider` | `openai` 2.x | `gpt-5.6-luna` |
|
|
44
53
|
|
|
45
54
|
## Key Design Decisions
|
|
46
55
|
|
|
47
56
|
- Gemini-specific capabilities are represented directly instead of being hidden behind a broad universal abstraction.
|
|
48
57
|
- JSON generation uses provider-native JSON configuration and still validates locally with `json.loads` and optional Pydantic models.
|
|
58
|
+
- OpenAI uses the Responses API exclusively; the old Chat Completions path is not retained.
|
|
59
|
+
- OpenAI requests default to `store=False` and `reasoning={"effort": "none"}`.
|
|
60
|
+
- OpenAI Pydantic schemas use the SDK's native `responses.parse()` structured-output path.
|
|
61
|
+
- The OpenAI SDK client is injectable behind a small Responses API port for deterministic tests and custom transports.
|
|
49
62
|
- Gemini image generation and Imagen generation are separate explicit methods because they use different SDK calls.
|
|
50
63
|
- `generate_image_bytes()` uses Gemini `generate_content` with image modality. Its `response_mime_type` is only a preferred/fallback MIME type, and Gemini image controls are passed through `image_config`.
|
|
64
|
+
- `generate_image_bytes()` can also accept `input_images=[ImageInput(...)]` for image reference/edit workflows; without `input_images`, the prompt-only path is unchanged.
|
|
51
65
|
- `generate_image_bytes()` retries a rejected `image_config={"image_size": "4K"}` request once as `2K`.
|
|
52
66
|
- `generate_imagen_bytes()` uses Imagen `generate_images` and passes the requested MIME as `output_mime_type`.
|
|
53
67
|
- Anthropic, OpenRouter, and multi-provider failover are not active APIs in this line.
|
|
@@ -33,6 +33,16 @@ prompt: str
|
|
|
33
33
|
-> (bytes, actual_mime_type)
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
With `input_images`, the provider sends multimodal content:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
prompt: str + input_images: list[ImageInput]
|
|
40
|
+
-> text part + inline_data image parts
|
|
41
|
+
-> GeminiProvider.generate_image_bytes()
|
|
42
|
+
-> first returned inline_data image part
|
|
43
|
+
-> (bytes, actual_mime_type)
|
|
44
|
+
```
|
|
45
|
+
|
|
36
46
|
`response_mime_type` is not passed to `GenerateContentConfig.response_mime_type` on this path; it is only a fallback content type when Gemini omits `inline_data.mime_type`.
|
|
37
47
|
When `image_config.image_size` is `4K` and Gemini rejects the request, the provider retries once with `image_size` changed to `2K`.
|
|
38
48
|
|
|
@@ -51,7 +61,30 @@ prompt: str
|
|
|
51
61
|
```
|
|
52
62
|
PromptResult | str
|
|
53
63
|
-> OpenAIProvider.generate_text()
|
|
54
|
-
->
|
|
55
|
-
->
|
|
64
|
+
-> _OpenAIRequestMapper
|
|
65
|
+
-> responses.create(store=False)
|
|
66
|
+
-> response.output_text
|
|
56
67
|
-> str
|
|
57
68
|
```
|
|
69
|
+
|
|
70
|
+
## OpenAI Structured JSON
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
PromptResult | str + Pydantic schema
|
|
74
|
+
-> OpenAIProvider.generate_json()
|
|
75
|
+
-> responses.parse(text_format=schema)
|
|
76
|
+
-> response.output_parsed
|
|
77
|
+
-> BaseModel
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Without a schema, the provider requests JSON mode and decodes `response.output_text`.
|
|
81
|
+
|
|
82
|
+
## OpenAI Streaming
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
PromptResult | str
|
|
86
|
+
-> OpenAIProvider.stream_text()
|
|
87
|
+
-> responses.create(stream=True)
|
|
88
|
+
-> response.output_text.delta events
|
|
89
|
+
-> AsyncIterator[str]
|
|
90
|
+
```
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Назначение
|
|
4
4
|
|
|
5
|
-
`codex_ai.providers` содержит конкретные SDK-адаптеры, а не широкий interchangeable provider framework. Фокус продукта — Gemini; OpenAI
|
|
5
|
+
`codex_ai.providers` содержит конкретные SDK-адаптеры, а не широкий interchangeable provider framework. Фокус продукта — Gemini; OpenAI является отдельным современным адаптером Responses API.
|
|
6
6
|
|
|
7
7
|
Gemini является основным направлением и дает прямые методы для текста, JSON и картинок:
|
|
8
8
|
|
|
@@ -13,7 +13,14 @@ await gemini.generate_image_bytes(...)
|
|
|
13
13
|
await gemini.generate_imagen_bytes(...)
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
OpenAI
|
|
16
|
+
OpenAI предоставляет прямые методы для текста, структурированного JSON и typed streaming:
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
await openai.generate_text(...)
|
|
20
|
+
await openai.generate_json(..., schema=MyModel)
|
|
21
|
+
async for chunk in openai.stream_text(...):
|
|
22
|
+
...
|
|
23
|
+
```
|
|
17
24
|
|
|
18
25
|
## Архитектура
|
|
19
26
|
|
|
@@ -24,7 +31,9 @@ PromptResult/String
|
|
|
24
31
|
├── GeminiProvider.generate_json(...) -> dict | BaseModel
|
|
25
32
|
├── GeminiProvider.generate_image_bytes(...) -> tuple[bytes, str]
|
|
26
33
|
├── GeminiProvider.generate_imagen_bytes(...) -> tuple[bytes, str]
|
|
27
|
-
|
|
34
|
+
├── OpenAIProvider.generate_text(...) -> str
|
|
35
|
+
├── OpenAIProvider.generate_json(...) -> dict | BaseModel
|
|
36
|
+
└── OpenAIProvider.stream_text(...) -> AsyncIterator[str]
|
|
28
37
|
```
|
|
29
38
|
|
|
30
39
|
Старый router pipeline остается доступным:
|
|
@@ -40,14 +49,19 @@ LLMRouter builder -> PromptResult -> LLMDispatcher.process() -> provider.answer(
|
|
|
40
49
|
| Компонент | Класс | SDK | Модель по умолчанию |
|
|
41
50
|
|-----------|-------|-----|---------------------|
|
|
42
51
|
| `gemini.py` | `GeminiProvider` | `google-genai` | `gemini-2.5-flash-lite` |
|
|
43
|
-
| `openai.py` | `OpenAIProvider` | `openai` | `gpt-
|
|
52
|
+
| `openai.py` | `OpenAIProvider` | `openai` 2.x | `gpt-5.6-luna` |
|
|
44
53
|
|
|
45
54
|
## Ключевые решения
|
|
46
55
|
|
|
47
56
|
- Возможности Gemini представлены напрямую, без широкой универсальной абстракции.
|
|
48
57
|
- JSON generation использует native JSON config провайдера и локальную проверку через `json.loads` и optional Pydantic schema.
|
|
58
|
+
- OpenAI использует только Responses API; старый путь Chat Completions не сохраняется.
|
|
59
|
+
- OpenAI-запросы по умолчанию используют `store=False` и `reasoning={"effort": "none"}`.
|
|
60
|
+
- Pydantic-схемы OpenAI обрабатываются через native structured-output путь `responses.parse()`.
|
|
61
|
+
- OpenAI SDK client можно внедрить через небольшой Responses API port для детерминированных тестов и custom transport.
|
|
49
62
|
- Gemini image generation и Imagen generation разведены в отдельные явные методы, потому что они используют разные SDK calls.
|
|
50
63
|
- `generate_image_bytes()` использует Gemini `generate_content` с image modality. Его `response_mime_type` является только preferred/fallback MIME type, а Gemini image controls передаются через `image_config`.
|
|
64
|
+
- `generate_image_bytes()` также принимает `input_images=[ImageInput(...)]` для сценариев reference/edit; без `input_images` старый prompt-only путь не меняется.
|
|
51
65
|
- `generate_image_bytes()` один раз повторяет отклоненный `image_config={"image_size": "4K"}` запрос как `2K`.
|
|
52
66
|
- `generate_imagen_bytes()` использует Imagen `generate_images` и передает requested MIME как `output_mime_type`.
|
|
53
67
|
- Anthropic, OpenRouter и multi-provider failover не являются активными API в этой линейке.
|
|
@@ -33,6 +33,16 @@ prompt: str
|
|
|
33
33
|
-> (bytes, actual_mime_type)
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
С `input_images` провайдер отправляет multimodal content:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
prompt: str + input_images: list[ImageInput]
|
|
40
|
+
-> text part + inline_data image parts
|
|
41
|
+
-> GeminiProvider.generate_image_bytes()
|
|
42
|
+
-> first returned inline_data image part
|
|
43
|
+
-> (bytes, actual_mime_type)
|
|
44
|
+
```
|
|
45
|
+
|
|
36
46
|
`response_mime_type` в этом пути не передается в `GenerateContentConfig.response_mime_type`; он используется только как fallback content type, если Gemini не вернул `inline_data.mime_type`.
|
|
37
47
|
Если `image_config.image_size` равен `4K` и Gemini отклоняет запрос, провайдер один раз повторяет его с `image_size="2K"`.
|
|
38
48
|
|
|
@@ -51,7 +61,30 @@ prompt: str
|
|
|
51
61
|
```
|
|
52
62
|
PromptResult | str
|
|
53
63
|
-> OpenAIProvider.generate_text()
|
|
54
|
-
->
|
|
55
|
-
->
|
|
64
|
+
-> _OpenAIRequestMapper
|
|
65
|
+
-> responses.create(store=False)
|
|
66
|
+
-> response.output_text
|
|
56
67
|
-> str
|
|
57
68
|
```
|
|
69
|
+
|
|
70
|
+
## OpenAI Structured JSON
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
PromptResult | str + Pydantic schema
|
|
74
|
+
-> OpenAIProvider.generate_json()
|
|
75
|
+
-> responses.parse(text_format=schema)
|
|
76
|
+
-> response.output_parsed
|
|
77
|
+
-> BaseModel
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Без schema провайдер запрашивает JSON mode и декодирует `response.output_text`.
|
|
81
|
+
|
|
82
|
+
## OpenAI Streaming
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
PromptResult | str
|
|
86
|
+
-> OpenAIProvider.stream_text()
|
|
87
|
+
-> responses.create(stream=True)
|
|
88
|
+
-> response.output_text.delta events
|
|
89
|
+
-> AsyncIterator[str]
|
|
90
|
+
```
|
|
@@ -30,7 +30,7 @@ Repository = "https://github.com/codexdlc/codex-ai"
|
|
|
30
30
|
Issues = "https://github.com/codexdlc/codex-ai/issues"
|
|
31
31
|
|
|
32
32
|
[project.optional-dependencies]
|
|
33
|
-
openai = ["openai>=
|
|
33
|
+
openai = ["openai>=2.0,<3.0"]
|
|
34
34
|
gemini = ["google-genai==2.3.0"]
|
|
35
35
|
all = [
|
|
36
36
|
"codex-ai[openai,gemini]",
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from codex_ai.core import (
|
|
4
4
|
ImageGenerationProvider,
|
|
5
|
+
ImageInput,
|
|
6
|
+
ImageInputLike,
|
|
5
7
|
ImagenGenerationProvider,
|
|
6
8
|
JsonGenerationProvider,
|
|
7
9
|
LLMDispatcher,
|
|
@@ -19,6 +21,8 @@ __all__ = [
|
|
|
19
21
|
# Core
|
|
20
22
|
"LLMDispatcher",
|
|
21
23
|
"ImageGenerationProvider",
|
|
24
|
+
"ImageInput",
|
|
25
|
+
"ImageInputLike",
|
|
22
26
|
"ImagenGenerationProvider",
|
|
23
27
|
"JsonGenerationProvider",
|
|
24
28
|
"LLMMessage",
|
|
@@ -8,6 +8,8 @@ from .dispatcher import LLMDispatcher
|
|
|
8
8
|
from .exceptions import LLMProviderError
|
|
9
9
|
from .protocol import (
|
|
10
10
|
ImageGenerationProvider,
|
|
11
|
+
ImageInput,
|
|
12
|
+
ImageInputLike,
|
|
11
13
|
ImagenGenerationProvider,
|
|
12
14
|
JsonGenerationProvider,
|
|
13
15
|
LLMMessage,
|
|
@@ -23,6 +25,8 @@ __all__ = [
|
|
|
23
25
|
"LLMDispatcher",
|
|
24
26
|
"LLMProviderError",
|
|
25
27
|
"ImageGenerationProvider",
|
|
28
|
+
"ImageInput",
|
|
29
|
+
"ImageInputLike",
|
|
26
30
|
"ImagenGenerationProvider",
|
|
27
31
|
"JsonGenerationProvider",
|
|
28
32
|
"LLMMessage",
|
|
@@ -14,6 +14,7 @@ from typing import Any
|
|
|
14
14
|
|
|
15
15
|
from .protocol import (
|
|
16
16
|
ImageGenerationProvider,
|
|
17
|
+
ImageInputLike,
|
|
17
18
|
ImagenGenerationProvider,
|
|
18
19
|
JsonGenerationProvider,
|
|
19
20
|
LLMProviderProtocol,
|
|
@@ -98,6 +99,7 @@ class LLMDispatcher:
|
|
|
98
99
|
model: str | None = None,
|
|
99
100
|
response_mime_type: str = "image/webp",
|
|
100
101
|
image_config: dict[str, Any] | None = None,
|
|
102
|
+
input_images: list[ImageInputLike] | None = None,
|
|
101
103
|
**kwargs: Any,
|
|
102
104
|
) -> tuple[bytes, str]:
|
|
103
105
|
"""
|
|
@@ -117,6 +119,7 @@ class LLMDispatcher:
|
|
|
117
119
|
model=model,
|
|
118
120
|
response_mime_type=response_mime_type,
|
|
119
121
|
image_config=image_config,
|
|
122
|
+
input_images=input_images,
|
|
120
123
|
**kwargs,
|
|
121
124
|
)
|
|
122
125
|
|
|
@@ -14,7 +14,7 @@ PromptBuilder — type alias for async builder functions registered via LLMRoute
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
-
from collections.abc import Awaitable, Callable
|
|
17
|
+
from collections.abc import Awaitable, Callable, Mapping
|
|
18
18
|
from typing import TYPE_CHECKING, Any, Literal, Protocol, runtime_checkable
|
|
19
19
|
|
|
20
20
|
from pydantic import BaseModel, ConfigDict
|
|
@@ -64,6 +64,24 @@ class PromptResult(BaseDTO):
|
|
|
64
64
|
max_tokens: int | None = None
|
|
65
65
|
|
|
66
66
|
|
|
67
|
+
class ImageInput(BaseDTO):
|
|
68
|
+
"""
|
|
69
|
+
Binary image input used as a reference or edit source for image generation.
|
|
70
|
+
|
|
71
|
+
Attributes:
|
|
72
|
+
data: Raw image bytes.
|
|
73
|
+
mime_type: Image MIME type, for example ``"image/png"`` or ``"image/jpeg"``.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True)
|
|
77
|
+
|
|
78
|
+
data: bytes
|
|
79
|
+
mime_type: str
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
ImageInputLike = ImageInput | Mapping[str, Any]
|
|
83
|
+
|
|
84
|
+
|
|
67
85
|
@runtime_checkable
|
|
68
86
|
class LLMProviderProtocol(Protocol):
|
|
69
87
|
"""
|
|
@@ -150,6 +168,7 @@ class ImageGenerationProvider(Protocol):
|
|
|
150
168
|
model: str | None = None,
|
|
151
169
|
response_mime_type: str = "image/webp",
|
|
152
170
|
image_config: dict[str, Any] | None = None,
|
|
171
|
+
input_images: list[ImageInputLike] | None = None,
|
|
153
172
|
**kwargs: Any,
|
|
154
173
|
) -> tuple[bytes, str]:
|
|
155
174
|
"""
|
|
@@ -161,6 +180,8 @@ class ImageGenerationProvider(Protocol):
|
|
|
161
180
|
response_mime_type: Requested/preferred image MIME type.
|
|
162
181
|
image_config: Optional image generation controls such as
|
|
163
182
|
``{"aspect_ratio": "1:1", "image_size": "4K"}``.
|
|
183
|
+
input_images: Optional reference/source images for image editing or
|
|
184
|
+
image-conditioned generation.
|
|
164
185
|
**kwargs: Extra provider-specific kwargs.
|
|
165
186
|
|
|
166
187
|
Returns:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
codex_ai.providers
|
|
3
3
|
==================
|
|
4
|
-
Provider adapters. Gemini is the primary API; OpenAI
|
|
4
|
+
Provider adapters. Gemini is the primary API; OpenAI uses the Responses API.
|
|
5
5
|
|
|
6
6
|
Providers are lazy-loaded to avoid mandatory dependency on all SDK packages.
|
|
7
7
|
"""
|