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.
Files changed (68) hide show
  1. {codex_ai-0.2.4 → codex_ai-0.2.5}/CHANGELOG.md +18 -0
  2. {codex_ai-0.2.4 → codex_ai-0.2.5}/PKG-INFO +46 -8
  3. {codex_ai-0.2.4 → codex_ai-0.2.5}/README.md +42 -4
  4. {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/en/architecture/providers/README.md +18 -4
  5. {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/en/architecture/providers/data_flow.md +35 -2
  6. {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/ru/architecture/providers/README.md +18 -4
  7. {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/ru/architecture/providers/data_flow.md +35 -2
  8. {codex_ai-0.2.4 → codex_ai-0.2.5}/pyproject.toml +1 -1
  9. {codex_ai-0.2.4 → codex_ai-0.2.5}/src/codex_ai/__init__.py +4 -0
  10. {codex_ai-0.2.4 → codex_ai-0.2.5}/src/codex_ai/core/__init__.py +4 -0
  11. {codex_ai-0.2.4 → codex_ai-0.2.5}/src/codex_ai/core/dispatcher.py +3 -0
  12. {codex_ai-0.2.4 → codex_ai-0.2.5}/src/codex_ai/core/protocol.py +22 -1
  13. {codex_ai-0.2.4 → codex_ai-0.2.5}/src/codex_ai/providers/__init__.py +1 -1
  14. {codex_ai-0.2.4 → codex_ai-0.2.5}/src/codex_ai/providers/gemini.py +41 -5
  15. codex_ai-0.2.5/src/codex_ai/providers/openai.py +265 -0
  16. {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/integration/test_providers_integration.py +3 -3
  17. {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/unit/core/test_dispatcher.py +12 -3
  18. {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/unit/core/test_protocol.py +9 -0
  19. {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/unit/providers/test_gemini_provider.py +29 -0
  20. codex_ai-0.2.5/tests/unit/providers/test_openai_provider.py +272 -0
  21. {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/unit/test_public_api.py +1 -0
  22. {codex_ai-0.2.4 → codex_ai-0.2.5}/uv.lock +103 -98
  23. codex_ai-0.2.4/src/codex_ai/providers/openai.py +0 -138
  24. codex_ai-0.2.4/tests/unit/providers/test_openai_provider.py +0 -283
  25. {codex_ai-0.2.4 → codex_ai-0.2.5}/.github/workflows/ci.yml +0 -0
  26. {codex_ai-0.2.4 → codex_ai-0.2.5}/.github/workflows/docs.yml +0 -0
  27. {codex_ai-0.2.4 → codex_ai-0.2.5}/.github/workflows/publish.yml +0 -0
  28. {codex_ai-0.2.4 → codex_ai-0.2.5}/.gitignore +0 -0
  29. {codex_ai-0.2.4 → codex_ai-0.2.5}/.nojekyll +0 -0
  30. {codex_ai-0.2.4 → codex_ai-0.2.5}/.pre-commit-config.yaml +0 -0
  31. {codex_ai-0.2.4 → codex_ai-0.2.5}/.python-version +0 -0
  32. {codex_ai-0.2.4 → codex_ai-0.2.5}/.secrets.baseline +0 -0
  33. {codex_ai-0.2.4 → codex_ai-0.2.5}/LICENSE +0 -0
  34. {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/changelog.md +0 -0
  35. {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/en/api/core/dispatcher.md +0 -0
  36. {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/en/api/core/exceptions.md +0 -0
  37. {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/en/api/core/protocol.md +0 -0
  38. {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/en/api/core/router.md +0 -0
  39. {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/en/api/core/sync.md +0 -0
  40. {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/en/api/index.md +0 -0
  41. {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/en/api/providers/gemini.md +0 -0
  42. {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/en/api/providers/openai.md +0 -0
  43. {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/en/architecture/core/README.md +0 -0
  44. {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/en/architecture/core/data_flow.md +0 -0
  45. {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/index.md +0 -0
  46. {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/ru/architecture/core/README.md +0 -0
  47. {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/ru/architecture/core/data_flow.md +0 -0
  48. {codex_ai-0.2.4 → codex_ai-0.2.5}/docs/stylesheets/extra.css +0 -0
  49. {codex_ai-0.2.4 → codex_ai-0.2.5}/mkdocs.yml +0 -0
  50. {codex_ai-0.2.4 → codex_ai-0.2.5}/src/codex_ai/core/exceptions.py +0 -0
  51. {codex_ai-0.2.4 → codex_ai-0.2.5}/src/codex_ai/core/router.py +0 -0
  52. {codex_ai-0.2.4 → codex_ai-0.2.5}/src/codex_ai/core/sync.py +0 -0
  53. {codex_ai-0.2.4 → codex_ai-0.2.5}/src/codex_ai/py.typed +0 -0
  54. {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/conftest.py +0 -0
  55. {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/integration/__init__.py +0 -0
  56. {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/integration/conftest.py +0 -0
  57. {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/unit/__init__.py +0 -0
  58. {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/unit/conftest.py +0 -0
  59. {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/unit/core/__init__.py +0 -0
  60. {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/unit/core/test_exceptions.py +0 -0
  61. {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/unit/core/test_router.py +0 -0
  62. {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/unit/core/test_sync.py +0 -0
  63. {codex_ai-0.2.4 → codex_ai-0.2.5}/tests/unit/providers/__init__.py +0 -0
  64. {codex_ai-0.2.4 → codex_ai-0.2.5}/tools/__init__.py +0 -0
  65. {codex_ai-0.2.4 → codex_ai-0.2.5}/tools/dev/README.md +0 -0
  66. {codex_ai-0.2.4 → codex_ai-0.2.5}/tools/dev/__init__.py +0 -0
  67. {codex_ai-0.2.4 → codex_ai-0.2.5}/tools/dev/check.py +0 -0
  68. {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.4
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<2.0,>=1.0; extra == 'all'
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<2.0,>=1.0; extra == 'dev'
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<2.0,>=1.0; extra == '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
  [![CI](https://github.com/codexdlc/codex-ai/actions/workflows/ci.yml/badge.svg?branch=develop)](https://github.com/codexdlc/codex-ai/actions/workflows/ci.yml)
53
53
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](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 remains as a small text-only adapter, and the router/dispatcher layer is kept for legacy text workflows.
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`. Use `generate_imagen_bytes()` for
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]` | Text-only OpenAI Chat Completions adapter |
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
  [![CI](https://github.com/codexdlc/codex-ai/actions/workflows/ci.yml/badge.svg?branch=develop)](https://github.com/codexdlc/codex-ai/actions/workflows/ci.yml)
6
6
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](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 remains as a small text-only adapter, and the router/dispatcher layer is kept for legacy text workflows.
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`. Use `generate_imagen_bytes()` for
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]` | Text-only OpenAI Chat Completions adapter |
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 retained only for text generation.
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 is kept as a text-only adapter with the same `generate_text(...)` convenience.
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
- └── OpenAIProvider.generate_text(...) -> str
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-4o-mini` |
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
- -> chat.completions.create()
55
- -> choices[0].message.content
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 оставлен как text-only адаптер с `generate_text(...)`.
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
- └── OpenAIProvider.generate_text(...) -> str
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-4o-mini` |
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
- -> chat.completions.create()
55
- -> choices[0].message.content
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>=1.0,<2.0"]
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 is text-only.
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
  """