codex-backend-sdk 0.3.8__tar.gz → 0.3.9__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 (40) hide show
  1. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/CHANGELOG.md +9 -0
  2. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/PKG-INFO +1 -1
  3. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/codex_backend_sdk/__init__.py +1 -1
  4. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/codex_backend_sdk/resources/openai_oauth.py +36 -1
  5. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/pyproject.toml +1 -1
  6. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/tests/test_openai_oauth_resources.py +20 -0
  7. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/uv.lock +1 -1
  8. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/.gitignore +0 -0
  9. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/LICENSE +0 -0
  10. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/README.md +0 -0
  11. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/codex_backend_sdk/_client.py +0 -0
  12. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/codex_backend_sdk/_models.py +0 -0
  13. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/codex_backend_sdk/_streaming.py +0 -0
  14. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/codex_backend_sdk/_transport.py +0 -0
  15. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/codex_backend_sdk/_utils.py +0 -0
  16. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/codex_backend_sdk/codex_client.py +0 -0
  17. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/codex_backend_sdk/oauth.py +0 -0
  18. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/codex_backend_sdk/pkce.py +0 -0
  19. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/codex_backend_sdk/resources/__init__.py +0 -0
  20. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/codex_backend_sdk/resources/_responses_payloads.py +0 -0
  21. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/codex_backend_sdk/resources/codex.py +0 -0
  22. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/codex_backend_sdk/resources/files.py +0 -0
  23. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/codex_backend_sdk/resources/models.py +0 -0
  24. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/codex_backend_sdk/resources/realtime.py +0 -0
  25. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/codex_backend_sdk/resources/responses.py +0 -0
  26. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/codex_backend_sdk/storage.py +0 -0
  27. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/docs/backend-api.md +0 -0
  28. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/examples/agent.py +0 -0
  29. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/tests/conftest.py +0 -0
  30. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/tests/test_account_info.py +0 -0
  31. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/tests/test_basic.py +0 -0
  32. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/tests/test_client_retry.py +0 -0
  33. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/tests/test_codex_resources.py +0 -0
  34. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/tests/test_conversation.py +0 -0
  35. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/tests/test_files_resource.py +0 -0
  36. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/tests/test_realtime_resource.py +0 -0
  37. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/tests/test_reasoning.py +0 -0
  38. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/tests/test_responses_resource.py +0 -0
  39. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/tests/test_structured_output.py +0 -0
  40. {codex_backend_sdk-0.3.8 → codex_backend_sdk-0.3.9}/tests/test_tools.py +0 -0
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.3.9] - 2026-07-17
6
+
7
+ ### Fixed
8
+ - Detect anonymous audio buffers by their file signature before ChatGPT transcription uploads, correcting filenames and MIME types when callers provide misleading generic names such as `audio.mp3` for WAV data.
9
+ - Restored reliable `AIClient.audio_to_text(bytes)` integration with `/backend-api/transcribe` without requiring callers to understand multipart backend constraints.
10
+
11
+ ### Tests
12
+ - Added regression coverage for WAV buffers carrying an incorrect `.mp3` name and verified the complete Codex Agent transcription path against the real ChatGPT backend.
13
+
5
14
  ## [0.3.8] - 2026-07-17
6
15
 
7
16
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codex-backend-sdk
3
- Version: 0.3.8
3
+ Version: 0.3.9
4
4
  Summary: Unofficial Python SDK for the ChatGPT Codex backend API
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -17,7 +17,7 @@ Quickstart:
17
17
  print(response.output_text)
18
18
  """
19
19
 
20
- __version__ = "0.3.8"
20
+ __version__ = "0.3.9"
21
21
 
22
22
  from .oauth import run_oauth_flow, refresh_access_token
23
23
  from .storage import load_tokens, save_tokens, TokenStore
@@ -126,7 +126,7 @@ class AudioTranscriptions:
126
126
 
127
127
  response = self._client._post_chatgpt_raw(
128
128
  "/transcribe",
129
- files={"file": _coerce_file(file)},
129
+ files={"file": _coerce_audio_file(file)},
130
130
  data={key: _form_value(value) for key, value in data.items()},
131
131
  headers=extra_headers,
132
132
  params=extra_query,
@@ -136,3 +136,38 @@ class AudioTranscriptions:
136
136
  if _is_given(response_format) and response_format == "text":
137
137
  return transcription.text
138
138
  return transcription
139
+
140
+
141
+ def _coerce_audio_file(file: Any) -> Any:
142
+ coerced = _coerce_file(file)
143
+ if isinstance(coerced, tuple):
144
+ return coerced
145
+ if not hasattr(coerced, "read"):
146
+ return coerced
147
+
148
+ position = coerced.tell() if hasattr(coerced, "tell") else None
149
+ header = coerced.read(12)
150
+ if position is not None and hasattr(coerced, "seek"):
151
+ coerced.seek(position)
152
+
153
+ detected = _audio_type(header)
154
+ if detected is None:
155
+ return coerced
156
+ extension, mime_type = detected
157
+ name = getattr(coerced, "name", "audio") or "audio"
158
+ stem = str(name).rsplit("/", 1)[-1].rsplit(".", 1)[0] or "audio"
159
+ return (f"{stem}.{extension}", coerced, mime_type)
160
+
161
+
162
+ def _audio_type(header: bytes) -> tuple[str, str] | None:
163
+ if header.startswith(b"RIFF") and header[8:12] == b"WAVE":
164
+ return "wav", "audio/wav"
165
+ if header.startswith(b"ID3") or header[:2] in {b"\xff\xfb", b"\xff\xf3", b"\xff\xf2"}:
166
+ return "mp3", "audio/mpeg"
167
+ if header.startswith(b"OggS"):
168
+ return "ogg", "audio/ogg"
169
+ if header.startswith(b"fLaC"):
170
+ return "flac", "audio/flac"
171
+ if header[4:8] == b"ftyp":
172
+ return "m4a", "audio/mp4"
173
+ return None
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "codex-backend-sdk"
7
- version = "0.3.8"
7
+ version = "0.3.9"
8
8
  description = "Unofficial Python SDK for the ChatGPT Codex backend API"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -1,3 +1,5 @@
1
+ from io import BytesIO
2
+
1
3
  import pytest
2
4
 
3
5
  from codex_backend_sdk import CreateEmbeddingResponse, OpenAI, Transcription
@@ -92,6 +94,24 @@ def test_audio_transcriptions_create_posts_to_chatgpt_backend():
92
94
  assert headers["ChatGPT-Account-ID"] == "account-id"
93
95
 
94
96
 
97
+ def test_audio_transcriptions_detects_anonymous_audio_format():
98
+ client = FakeOpenAIClient()
99
+ audio = BytesIO(b"RIFF\x00\x00\x00\x00WAVEfmt ")
100
+ audio.name = "audio.mp3"
101
+
102
+ client.audio.transcriptions.create(
103
+ model="gpt-4o-mini-transcribe",
104
+ file=audio,
105
+ )
106
+
107
+ _, kwargs, _ = client.chatgpt_raw_posts[0]
108
+ filename, uploaded, mime_type = kwargs["files"]["file"]
109
+ assert filename == "audio.wav"
110
+ assert uploaded is audio
111
+ assert mime_type == "audio/wav"
112
+ assert audio.tell() == 0
113
+
114
+
95
115
  def test_audio_transcriptions_text_format_returns_string():
96
116
  client = FakeOpenAIClient()
97
117
 
@@ -160,7 +160,7 @@ wheels = [
160
160
 
161
161
  [[package]]
162
162
  name = "codex-backend-sdk"
163
- version = "0.3.8"
163
+ version = "0.3.9"
164
164
  source = { editable = "." }
165
165
  dependencies = [
166
166
  { name = "pydantic" },