codex-agent-framework 0.1.47__tar.gz → 0.1.49__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 (151) hide show
  1. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/CHANGELOG.md +17 -0
  2. {codex_agent_framework-0.1.47/codex_agent_framework.egg-info → codex_agent_framework-0.1.49}/PKG-INFO +1 -1
  3. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/ai.py +39 -16
  4. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/config.py +1 -0
  5. codex_agent_framework-0.1.49/codex_agent/version.py +1 -0
  6. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49/codex_agent_framework.egg-info}/PKG-INFO +1 -1
  7. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/pyproject.toml +1 -1
  8. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_ai.py +43 -0
  9. codex_agent_framework-0.1.47/codex_agent/version.py +0 -1
  10. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/LICENSE +0 -0
  11. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/MANIFEST.in +0 -0
  12. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/README.md +0 -0
  13. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/__init__.py +0 -0
  14. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/__main__.py +0 -0
  15. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/agent.py +0 -0
  16. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/agent_runtime.py +0 -0
  17. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/browser.py +0 -0
  18. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_commands.py +0 -0
  19. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/__init__.py +0 -0
  20. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/bash/__init__.py +0 -0
  21. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/bash/requirements.txt +0 -0
  22. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/browser/__init__.py +0 -0
  23. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/browser/requirements.txt +0 -0
  24. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/content/__init__.py +0 -0
  25. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/content/requirements.txt +0 -0
  26. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/content/system.py +0 -0
  27. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/content/vision.py +0 -0
  28. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/context/__init__.py +0 -0
  29. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/desktop/__init__.py +0 -0
  30. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/desktop/requirements.txt +0 -0
  31. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/environment/__init__.py +0 -0
  32. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/environment/requirements.txt +0 -0
  33. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/files/__init__.py +0 -0
  34. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/files/requirements.txt +0 -0
  35. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/files/tools.py +0 -0
  36. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/image_generation/__init__.py +0 -0
  37. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/image_generation/image_generation_system_prompt.txt +0 -0
  38. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/image_generation/requirements.txt +0 -0
  39. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/interface/__init__.py +0 -0
  40. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/interface/requirements.txt +0 -0
  41. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/memory/__init__.py +0 -0
  42. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/memory/requirements.txt +0 -0
  43. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/planner/__init__.py +0 -0
  44. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/planner/requirements.txt +0 -0
  45. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/python/__init__.py +0 -0
  46. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/python/requirements.txt +0 -0
  47. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/realtime/__init__.py +0 -0
  48. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/realtime/audio.py +0 -0
  49. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/realtime/bridge.py +0 -0
  50. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/realtime/manager.py +0 -0
  51. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/realtime/requirements.txt +0 -0
  52. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/realtime/session.py +0 -0
  53. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/scheduler/__init__.py +0 -0
  54. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/scheduler/requirements.txt +0 -0
  55. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/subagents/__init__.py +0 -0
  56. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/subagents/profiles/explorer.json +0 -0
  57. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/subagents/requirements.txt +0 -0
  58. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/tts/__init__.py +0 -0
  59. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/builtin_plugins/tts/requirements.txt +0 -0
  60. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/cli/__init__.py +0 -0
  61. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/cli/headless.py +0 -0
  62. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/cli/main.py +0 -0
  63. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/cli/output.py +0 -0
  64. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/cli/root.py +0 -0
  65. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/cli/runner.py +0 -0
  66. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/client.py +0 -0
  67. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/command.py +0 -0
  68. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/context.py +0 -0
  69. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/desktop.py +0 -0
  70. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/event.py +0 -0
  71. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/get_text/__init__.py +0 -0
  72. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/get_text/default_gitignore +0 -0
  73. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/get_text/get_text.py +0 -0
  74. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/get_text/simpler_get_text.py +0 -0
  75. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/hooks.py +0 -0
  76. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/image.py +0 -0
  77. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/latex.py +0 -0
  78. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/mainloop.py +0 -0
  79. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/message.py +0 -0
  80. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/plugin.py +0 -0
  81. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/prompts/system_prompt.txt +0 -0
  82. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/provider.py +0 -0
  83. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/runtime.py +0 -0
  84. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/scheduler.py +0 -0
  85. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/scripts/install-system-dependencies.sh +0 -0
  86. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/server/__init__.py +0 -0
  87. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/server/app.py +0 -0
  88. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/server/core.py +0 -0
  89. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/service.py +0 -0
  90. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/sessions.py +0 -0
  91. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/status.py +0 -0
  92. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/stream.py +0 -0
  93. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/stream_utils.py +0 -0
  94. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/tool.py +0 -0
  95. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/tray.py +0 -0
  96. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/tui/__init__.py +0 -0
  97. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/tui/app.py +0 -0
  98. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/tui/chat.py +0 -0
  99. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/tui/lifecycle.py +0 -0
  100. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/tui/log.py +0 -0
  101. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/tui/state.py +0 -0
  102. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/tui/status_bar.py +0 -0
  103. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/tui/style.py +0 -0
  104. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/tui/transcript.py +0 -0
  105. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/utils.py +0 -0
  106. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent/worker.py +0 -0
  107. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent_framework.egg-info/SOURCES.txt +0 -0
  108. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent_framework.egg-info/dependency_links.txt +0 -0
  109. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent_framework.egg-info/entry_points.txt +0 -0
  110. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent_framework.egg-info/requires.txt +0 -0
  111. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/codex_agent_framework.egg-info/top_level.txt +0 -0
  112. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/dependencies.txt +0 -0
  113. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/setup.cfg +0 -0
  114. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_agent.py +0 -0
  115. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_agent_audio_hooks.py +0 -0
  116. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_agent_commands.py +0 -0
  117. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_agent_context.py +0 -0
  118. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_agent_desktop.py +0 -0
  119. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_agent_environment.py +0 -0
  120. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_agent_image_generation.py +0 -0
  121. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_agent_observe.py +0 -0
  122. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_agent_plugins.py +0 -0
  123. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_agent_providers_config.py +0 -0
  124. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_agent_realtime.py +0 -0
  125. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_agent_response.py +0 -0
  126. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_agent_runtime.py +0 -0
  127. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_agent_sessions.py +0 -0
  128. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_agent_subagents.py +0 -0
  129. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_agent_tools.py +0 -0
  130. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_agent_turns.py +0 -0
  131. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_browser.py +0 -0
  132. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_builtin_config.py +0 -0
  133. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_chat.py +0 -0
  134. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_cli.py +0 -0
  135. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_cli_headless.py +0 -0
  136. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_cli_root.py +0 -0
  137. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_client.py +0 -0
  138. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_events.py +0 -0
  139. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_get_text_browser.py +0 -0
  140. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_hooks.py +0 -0
  141. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_image_message.py +0 -0
  142. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_local_desktop.py +0 -0
  143. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_memory.py +0 -0
  144. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_messages.py +0 -0
  145. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_planner.py +0 -0
  146. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_scheduler.py +0 -0
  147. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_server.py +0 -0
  148. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_service.py +0 -0
  149. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_tui.py +0 -0
  150. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_utils.py +0 -0
  151. {codex_agent_framework-0.1.47 → codex_agent_framework-0.1.49}/tests/test_worker.py +0 -0
@@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  This project loosely follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and uses semantic versioning where practical.
6
6
 
7
+ ## [0.1.49] - 2026-05-16
8
+ ### Changed
9
+ - Add a byte-compilation guard to the offline CI script so `codex_agent` and `tests` are syntax-checked before the explicit pytest subset runs.
10
+ - Update development docs to describe the local compile/test CI subset.
11
+
12
+ ### Tests
13
+ - Validate with the compileall guard, the 556-test offline CI suite, package build, and `twine check`.
14
+
15
+ ## [0.1.48] - 2026-05-16
16
+ ### Changed
17
+ - Make OpenAI API key validation configurable with `validate_openai_api_key` while preserving validation by default.
18
+ - Cache successful or failed OpenAI API key validation by key fingerprint to avoid repeated `models.list()` checks across clients using the same key.
19
+ - Update development docs for the current 556-test offline CI count.
20
+
21
+ ### Tests
22
+ - Add coverage for cached OpenAI key validation and disabled validation, then validate with targeted AI/config/response coverage plus the 556-test offline CI suite.
23
+
7
24
  ## [0.1.47] - 2026-05-16
8
25
  ### Changed
9
26
  - Document the current alpha public API contract for SDK and plugin users, including preferred surfaces such as `Agent`, `AgentTurn`, decorators, `Plugin`, hooks, events, and `agent.add_*` helpers.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codex-agent-framework
3
- Version: 0.1.47
3
+ Version: 0.1.49
4
4
  Summary: A lightweight event-driven Codex agent runtime.
5
5
  Author: Baptiste
6
6
  License-Expression: MIT
@@ -1,3 +1,4 @@
1
+ import hashlib
1
2
  import queue
2
3
  import threading
3
4
 
@@ -61,6 +62,9 @@ MAX_EMBEDDING_INPUT_TOKENS = 7500
61
62
  class AIClient:
62
63
  """Client for Codex backend LLM/embedding/STT calls plus OpenAI-key TTS/token helpers."""
63
64
 
65
+ _api_key_validation_cache = {}
66
+ _api_key_validation_lock = threading.Lock()
67
+
64
68
  def __init__(self, agent):
65
69
  """Initialize AI clients from agent config.
66
70
 
@@ -74,15 +78,15 @@ class AIClient:
74
78
  @property
75
79
  def client(self):
76
80
  if self._client is None:
77
- api_key=self.agent.config.get('openai_api_key') or os.getenv('OPENAI_API_KEY')
81
+ api_key = self.openai_api_key()
78
82
  try:
79
83
  self._client = OpenAI(api_key=api_key, timeout=180, max_retries=3)
80
- if not self.api_key_is_valid():
81
- self._client=None
82
- raise APIAuthenticationError("Missing or invalid OpenAI API key. You can pass it when creating the agent with `agent=Agent(openai_api_key='your_api_key')` or via `agent.config.openai_api_key` or set the OPENAI_API_KEY environment variable. You can get an API key at https://platform.openai.com/account/api-keys.")
84
+ if self.agent.config.get("validate_openai_api_key", True) and not self.api_key_is_valid(api_key):
85
+ self._client = None
86
+ raise self.authentication_error()
83
87
  except OpenAIError:
84
- self._client=None
85
- raise APIAuthenticationError("Missing or invalid OpenAI API key. You can pass it when creating the agent with `agent=Agent(openai_api_key='your_api_key')` or via `agent.config.openai_api_key` or set the OPENAI_API_KEY environment variable. You can get an API key at https://platform.openai.com/account/api-keys.")
88
+ self._client = None
89
+ raise self.authentication_error()
86
90
  return self._client
87
91
 
88
92
  @property
@@ -97,19 +101,38 @@ class AIClient:
97
101
  if self._codex_client is not None and hasattr(self._codex_client, "abort"):
98
102
  self._codex_client.abort()
99
103
 
100
- def api_key_is_valid(self):
101
- """
102
- Verify that the API key is valid by making a minimal API call.
104
+ def openai_api_key(self):
105
+ return self.agent.config.get("openai_api_key") or os.getenv("OPENAI_API_KEY")
106
+
107
+ def authentication_error(self):
108
+ return APIAuthenticationError(
109
+ "Missing or invalid OpenAI API key. You can pass it when creating the agent with "
110
+ "`agent=Agent(openai_api_key='your_api_key')` or via `agent.config.openai_api_key` "
111
+ "or set the OPENAI_API_KEY environment variable. You can get an API key at "
112
+ "https://platform.openai.com/account/api-keys."
113
+ )
114
+
115
+ def api_key_cache_key(self, api_key):
116
+ return hashlib.sha256((api_key or "").encode()).hexdigest()
117
+
118
+ def api_key_is_valid(self, api_key=None):
119
+ """Verify the OpenAI API key once per key value."""
120
+ api_key = self.openai_api_key() if api_key is None else api_key
121
+ cache_key = self.api_key_cache_key(api_key)
122
+ with self._api_key_validation_lock:
123
+ if cache_key in self._api_key_validation_cache:
124
+ return self._api_key_validation_cache[cache_key]
103
125
 
104
- Returns:
105
- bool: True if API key is valid, False otherwise
106
- """
107
126
  try:
108
- # Make a minimal API call to verify the key
109
- self.client.models.list()
110
- return True
127
+ validation_client = self._client or OpenAI(api_key=api_key, timeout=180, max_retries=3)
128
+ validation_client.models.list()
129
+ valid = True
111
130
  except Exception:
112
- return False
131
+ valid = False
132
+
133
+ with self._api_key_validation_lock:
134
+ self._api_key_validation_cache[cache_key] = valid
135
+ return valid
113
136
 
114
137
  def text_to_audio(self, model=None, voice=None, input='', voice_instructions='', **kwargs):
115
138
  """
@@ -19,6 +19,7 @@ class AgentConfig(modict):
19
19
  system = DEFAULT_SYSTEM_PROMPT
20
20
  auto_proceed = True
21
21
  openai_api_key = None
22
+ validate_openai_api_key = True
22
23
  history = None
23
24
  name = "Pandora"
24
25
  username = "Unknown"
@@ -0,0 +1 @@
1
+ __version__ = "0.1.49"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codex-agent-framework
3
- Version: 0.1.47
3
+ Version: 0.1.49
4
4
  Summary: A lightweight event-driven Codex agent runtime.
5
5
  Author: Baptiste
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "codex-agent-framework"
7
- version = "0.1.47"
7
+ version = "0.1.49"
8
8
  description = "A lightweight event-driven Codex agent runtime."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -84,6 +84,49 @@ class FakeAgent:
84
84
  self.mainloop_manager = FakeMainLoopManager()
85
85
 
86
86
 
87
+ class FakeModels:
88
+ def __init__(self):
89
+ self.calls = 0
90
+
91
+ def list(self):
92
+ self.calls += 1
93
+ return []
94
+
95
+
96
+ class FakeOpenAIValidationClient:
97
+ instances = []
98
+
99
+ def __init__(self, **kwargs):
100
+ self.kwargs = kwargs
101
+ self.models = FakeModels()
102
+ self.__class__.instances.append(self)
103
+
104
+
105
+ def test_openai_api_key_validation_is_cached_by_key(monkeypatch):
106
+ AIClient._api_key_validation_cache.clear()
107
+ FakeOpenAIValidationClient.instances = []
108
+ monkeypatch.setattr("codex_agent.ai.OpenAI", FakeOpenAIValidationClient)
109
+
110
+ first = AIClient(FakeAgent(openai_api_key="sk-test"))
111
+ second = AIClient(FakeAgent(openai_api_key="sk-test"))
112
+
113
+ assert first.client is FakeOpenAIValidationClient.instances[0]
114
+ assert second.client is FakeOpenAIValidationClient.instances[1]
115
+ assert FakeOpenAIValidationClient.instances[0].models.calls == 1
116
+ assert FakeOpenAIValidationClient.instances[1].models.calls == 0
117
+
118
+
119
+ def test_openai_api_key_validation_can_be_disabled(monkeypatch):
120
+ AIClient._api_key_validation_cache.clear()
121
+ FakeOpenAIValidationClient.instances = []
122
+ monkeypatch.setattr("codex_agent.ai.OpenAI", FakeOpenAIValidationClient)
123
+
124
+ ai = AIClient(FakeAgent(openai_api_key="sk-test", validate_openai_api_key=False))
125
+
126
+ assert ai.client is FakeOpenAIValidationClient.instances[0]
127
+ assert FakeOpenAIValidationClient.instances[0].models.calls == 0
128
+
129
+
87
130
  def test_embed_content_truncates_input_to_embedding_token_budget():
88
131
  ai = AIClient(agent=None)
89
132
  ai._codex_client = FakeCodexClient()
@@ -1 +0,0 @@
1
- __version__ = "0.1.47"