gobby 0.2.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (383) hide show
  1. gobby/__init__.py +3 -0
  2. gobby/adapters/__init__.py +30 -0
  3. gobby/adapters/base.py +93 -0
  4. gobby/adapters/claude_code.py +276 -0
  5. gobby/adapters/codex.py +1292 -0
  6. gobby/adapters/gemini.py +343 -0
  7. gobby/agents/__init__.py +37 -0
  8. gobby/agents/codex_session.py +120 -0
  9. gobby/agents/constants.py +112 -0
  10. gobby/agents/context.py +362 -0
  11. gobby/agents/definitions.py +133 -0
  12. gobby/agents/gemini_session.py +111 -0
  13. gobby/agents/registry.py +618 -0
  14. gobby/agents/runner.py +968 -0
  15. gobby/agents/session.py +259 -0
  16. gobby/agents/spawn.py +916 -0
  17. gobby/agents/spawners/__init__.py +77 -0
  18. gobby/agents/spawners/base.py +142 -0
  19. gobby/agents/spawners/cross_platform.py +266 -0
  20. gobby/agents/spawners/embedded.py +225 -0
  21. gobby/agents/spawners/headless.py +226 -0
  22. gobby/agents/spawners/linux.py +125 -0
  23. gobby/agents/spawners/macos.py +277 -0
  24. gobby/agents/spawners/windows.py +308 -0
  25. gobby/agents/tty_config.py +319 -0
  26. gobby/autonomous/__init__.py +32 -0
  27. gobby/autonomous/progress_tracker.py +447 -0
  28. gobby/autonomous/stop_registry.py +269 -0
  29. gobby/autonomous/stuck_detector.py +383 -0
  30. gobby/cli/__init__.py +67 -0
  31. gobby/cli/__main__.py +8 -0
  32. gobby/cli/agents.py +529 -0
  33. gobby/cli/artifacts.py +266 -0
  34. gobby/cli/daemon.py +329 -0
  35. gobby/cli/extensions.py +526 -0
  36. gobby/cli/github.py +263 -0
  37. gobby/cli/init.py +53 -0
  38. gobby/cli/install.py +614 -0
  39. gobby/cli/installers/__init__.py +37 -0
  40. gobby/cli/installers/antigravity.py +65 -0
  41. gobby/cli/installers/claude.py +363 -0
  42. gobby/cli/installers/codex.py +192 -0
  43. gobby/cli/installers/gemini.py +294 -0
  44. gobby/cli/installers/git_hooks.py +377 -0
  45. gobby/cli/installers/shared.py +737 -0
  46. gobby/cli/linear.py +250 -0
  47. gobby/cli/mcp.py +30 -0
  48. gobby/cli/mcp_proxy.py +698 -0
  49. gobby/cli/memory.py +304 -0
  50. gobby/cli/merge.py +384 -0
  51. gobby/cli/projects.py +79 -0
  52. gobby/cli/sessions.py +622 -0
  53. gobby/cli/tasks/__init__.py +30 -0
  54. gobby/cli/tasks/_utils.py +658 -0
  55. gobby/cli/tasks/ai.py +1025 -0
  56. gobby/cli/tasks/commits.py +169 -0
  57. gobby/cli/tasks/crud.py +685 -0
  58. gobby/cli/tasks/deps.py +135 -0
  59. gobby/cli/tasks/labels.py +63 -0
  60. gobby/cli/tasks/main.py +273 -0
  61. gobby/cli/tasks/search.py +178 -0
  62. gobby/cli/tui.py +34 -0
  63. gobby/cli/utils.py +513 -0
  64. gobby/cli/workflows.py +927 -0
  65. gobby/cli/worktrees.py +481 -0
  66. gobby/config/__init__.py +129 -0
  67. gobby/config/app.py +551 -0
  68. gobby/config/extensions.py +167 -0
  69. gobby/config/features.py +472 -0
  70. gobby/config/llm_providers.py +98 -0
  71. gobby/config/logging.py +66 -0
  72. gobby/config/mcp.py +346 -0
  73. gobby/config/persistence.py +247 -0
  74. gobby/config/servers.py +141 -0
  75. gobby/config/sessions.py +250 -0
  76. gobby/config/tasks.py +784 -0
  77. gobby/hooks/__init__.py +104 -0
  78. gobby/hooks/artifact_capture.py +213 -0
  79. gobby/hooks/broadcaster.py +243 -0
  80. gobby/hooks/event_handlers.py +723 -0
  81. gobby/hooks/events.py +218 -0
  82. gobby/hooks/git.py +169 -0
  83. gobby/hooks/health_monitor.py +171 -0
  84. gobby/hooks/hook_manager.py +856 -0
  85. gobby/hooks/hook_types.py +575 -0
  86. gobby/hooks/plugins.py +813 -0
  87. gobby/hooks/session_coordinator.py +396 -0
  88. gobby/hooks/verification_runner.py +268 -0
  89. gobby/hooks/webhooks.py +339 -0
  90. gobby/install/claude/commands/gobby/bug.md +51 -0
  91. gobby/install/claude/commands/gobby/chore.md +51 -0
  92. gobby/install/claude/commands/gobby/epic.md +52 -0
  93. gobby/install/claude/commands/gobby/eval.md +235 -0
  94. gobby/install/claude/commands/gobby/feat.md +49 -0
  95. gobby/install/claude/commands/gobby/nit.md +52 -0
  96. gobby/install/claude/commands/gobby/ref.md +52 -0
  97. gobby/install/claude/hooks/HOOK_SCHEMAS.md +632 -0
  98. gobby/install/claude/hooks/hook_dispatcher.py +364 -0
  99. gobby/install/claude/hooks/validate_settings.py +102 -0
  100. gobby/install/claude/hooks-template.json +118 -0
  101. gobby/install/codex/hooks/hook_dispatcher.py +153 -0
  102. gobby/install/codex/prompts/forget.md +7 -0
  103. gobby/install/codex/prompts/memories.md +7 -0
  104. gobby/install/codex/prompts/recall.md +7 -0
  105. gobby/install/codex/prompts/remember.md +13 -0
  106. gobby/install/gemini/hooks/hook_dispatcher.py +268 -0
  107. gobby/install/gemini/hooks-template.json +138 -0
  108. gobby/install/shared/plugins/code_guardian.py +456 -0
  109. gobby/install/shared/plugins/example_notify.py +331 -0
  110. gobby/integrations/__init__.py +10 -0
  111. gobby/integrations/github.py +145 -0
  112. gobby/integrations/linear.py +145 -0
  113. gobby/llm/__init__.py +40 -0
  114. gobby/llm/base.py +120 -0
  115. gobby/llm/claude.py +578 -0
  116. gobby/llm/claude_executor.py +503 -0
  117. gobby/llm/codex.py +322 -0
  118. gobby/llm/codex_executor.py +513 -0
  119. gobby/llm/executor.py +316 -0
  120. gobby/llm/factory.py +34 -0
  121. gobby/llm/gemini.py +258 -0
  122. gobby/llm/gemini_executor.py +339 -0
  123. gobby/llm/litellm.py +287 -0
  124. gobby/llm/litellm_executor.py +303 -0
  125. gobby/llm/resolver.py +499 -0
  126. gobby/llm/service.py +236 -0
  127. gobby/mcp_proxy/__init__.py +29 -0
  128. gobby/mcp_proxy/actions.py +175 -0
  129. gobby/mcp_proxy/daemon_control.py +198 -0
  130. gobby/mcp_proxy/importer.py +436 -0
  131. gobby/mcp_proxy/lazy.py +325 -0
  132. gobby/mcp_proxy/manager.py +798 -0
  133. gobby/mcp_proxy/metrics.py +609 -0
  134. gobby/mcp_proxy/models.py +139 -0
  135. gobby/mcp_proxy/registries.py +215 -0
  136. gobby/mcp_proxy/schema_hash.py +381 -0
  137. gobby/mcp_proxy/semantic_search.py +706 -0
  138. gobby/mcp_proxy/server.py +549 -0
  139. gobby/mcp_proxy/services/__init__.py +0 -0
  140. gobby/mcp_proxy/services/fallback.py +306 -0
  141. gobby/mcp_proxy/services/recommendation.py +224 -0
  142. gobby/mcp_proxy/services/server_mgmt.py +214 -0
  143. gobby/mcp_proxy/services/system.py +72 -0
  144. gobby/mcp_proxy/services/tool_filter.py +231 -0
  145. gobby/mcp_proxy/services/tool_proxy.py +309 -0
  146. gobby/mcp_proxy/stdio.py +565 -0
  147. gobby/mcp_proxy/tools/__init__.py +27 -0
  148. gobby/mcp_proxy/tools/agents.py +1103 -0
  149. gobby/mcp_proxy/tools/artifacts.py +207 -0
  150. gobby/mcp_proxy/tools/hub.py +335 -0
  151. gobby/mcp_proxy/tools/internal.py +337 -0
  152. gobby/mcp_proxy/tools/memory.py +543 -0
  153. gobby/mcp_proxy/tools/merge.py +422 -0
  154. gobby/mcp_proxy/tools/metrics.py +283 -0
  155. gobby/mcp_proxy/tools/orchestration/__init__.py +23 -0
  156. gobby/mcp_proxy/tools/orchestration/cleanup.py +619 -0
  157. gobby/mcp_proxy/tools/orchestration/monitor.py +380 -0
  158. gobby/mcp_proxy/tools/orchestration/orchestrate.py +746 -0
  159. gobby/mcp_proxy/tools/orchestration/review.py +736 -0
  160. gobby/mcp_proxy/tools/orchestration/utils.py +16 -0
  161. gobby/mcp_proxy/tools/session_messages.py +1056 -0
  162. gobby/mcp_proxy/tools/task_dependencies.py +219 -0
  163. gobby/mcp_proxy/tools/task_expansion.py +591 -0
  164. gobby/mcp_proxy/tools/task_github.py +393 -0
  165. gobby/mcp_proxy/tools/task_linear.py +379 -0
  166. gobby/mcp_proxy/tools/task_orchestration.py +77 -0
  167. gobby/mcp_proxy/tools/task_readiness.py +522 -0
  168. gobby/mcp_proxy/tools/task_sync.py +351 -0
  169. gobby/mcp_proxy/tools/task_validation.py +843 -0
  170. gobby/mcp_proxy/tools/tasks/__init__.py +25 -0
  171. gobby/mcp_proxy/tools/tasks/_context.py +112 -0
  172. gobby/mcp_proxy/tools/tasks/_crud.py +516 -0
  173. gobby/mcp_proxy/tools/tasks/_factory.py +176 -0
  174. gobby/mcp_proxy/tools/tasks/_helpers.py +129 -0
  175. gobby/mcp_proxy/tools/tasks/_lifecycle.py +517 -0
  176. gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +301 -0
  177. gobby/mcp_proxy/tools/tasks/_resolution.py +55 -0
  178. gobby/mcp_proxy/tools/tasks/_search.py +215 -0
  179. gobby/mcp_proxy/tools/tasks/_session.py +125 -0
  180. gobby/mcp_proxy/tools/workflows.py +973 -0
  181. gobby/mcp_proxy/tools/worktrees.py +1264 -0
  182. gobby/mcp_proxy/transports/__init__.py +0 -0
  183. gobby/mcp_proxy/transports/base.py +95 -0
  184. gobby/mcp_proxy/transports/factory.py +44 -0
  185. gobby/mcp_proxy/transports/http.py +139 -0
  186. gobby/mcp_proxy/transports/stdio.py +213 -0
  187. gobby/mcp_proxy/transports/websocket.py +136 -0
  188. gobby/memory/backends/__init__.py +116 -0
  189. gobby/memory/backends/mem0.py +408 -0
  190. gobby/memory/backends/memu.py +485 -0
  191. gobby/memory/backends/null.py +111 -0
  192. gobby/memory/backends/openmemory.py +537 -0
  193. gobby/memory/backends/sqlite.py +304 -0
  194. gobby/memory/context.py +87 -0
  195. gobby/memory/manager.py +1001 -0
  196. gobby/memory/protocol.py +451 -0
  197. gobby/memory/search/__init__.py +66 -0
  198. gobby/memory/search/text.py +127 -0
  199. gobby/memory/viz.py +258 -0
  200. gobby/prompts/__init__.py +13 -0
  201. gobby/prompts/defaults/expansion/system.md +119 -0
  202. gobby/prompts/defaults/expansion/user.md +48 -0
  203. gobby/prompts/defaults/external_validation/agent.md +72 -0
  204. gobby/prompts/defaults/external_validation/external.md +63 -0
  205. gobby/prompts/defaults/external_validation/spawn.md +83 -0
  206. gobby/prompts/defaults/external_validation/system.md +6 -0
  207. gobby/prompts/defaults/features/import_mcp.md +22 -0
  208. gobby/prompts/defaults/features/import_mcp_github.md +17 -0
  209. gobby/prompts/defaults/features/import_mcp_search.md +16 -0
  210. gobby/prompts/defaults/features/recommend_tools.md +32 -0
  211. gobby/prompts/defaults/features/recommend_tools_hybrid.md +35 -0
  212. gobby/prompts/defaults/features/recommend_tools_llm.md +30 -0
  213. gobby/prompts/defaults/features/server_description.md +20 -0
  214. gobby/prompts/defaults/features/server_description_system.md +6 -0
  215. gobby/prompts/defaults/features/task_description.md +31 -0
  216. gobby/prompts/defaults/features/task_description_system.md +6 -0
  217. gobby/prompts/defaults/features/tool_summary.md +17 -0
  218. gobby/prompts/defaults/features/tool_summary_system.md +6 -0
  219. gobby/prompts/defaults/research/step.md +58 -0
  220. gobby/prompts/defaults/validation/criteria.md +47 -0
  221. gobby/prompts/defaults/validation/validate.md +38 -0
  222. gobby/prompts/loader.py +346 -0
  223. gobby/prompts/models.py +113 -0
  224. gobby/py.typed +0 -0
  225. gobby/runner.py +488 -0
  226. gobby/search/__init__.py +23 -0
  227. gobby/search/protocol.py +104 -0
  228. gobby/search/tfidf.py +232 -0
  229. gobby/servers/__init__.py +7 -0
  230. gobby/servers/http.py +636 -0
  231. gobby/servers/models.py +31 -0
  232. gobby/servers/routes/__init__.py +23 -0
  233. gobby/servers/routes/admin.py +416 -0
  234. gobby/servers/routes/dependencies.py +118 -0
  235. gobby/servers/routes/mcp/__init__.py +24 -0
  236. gobby/servers/routes/mcp/hooks.py +135 -0
  237. gobby/servers/routes/mcp/plugins.py +121 -0
  238. gobby/servers/routes/mcp/tools.py +1337 -0
  239. gobby/servers/routes/mcp/webhooks.py +159 -0
  240. gobby/servers/routes/sessions.py +582 -0
  241. gobby/servers/websocket.py +766 -0
  242. gobby/sessions/__init__.py +13 -0
  243. gobby/sessions/analyzer.py +322 -0
  244. gobby/sessions/lifecycle.py +240 -0
  245. gobby/sessions/manager.py +563 -0
  246. gobby/sessions/processor.py +225 -0
  247. gobby/sessions/summary.py +532 -0
  248. gobby/sessions/transcripts/__init__.py +41 -0
  249. gobby/sessions/transcripts/base.py +125 -0
  250. gobby/sessions/transcripts/claude.py +386 -0
  251. gobby/sessions/transcripts/codex.py +143 -0
  252. gobby/sessions/transcripts/gemini.py +195 -0
  253. gobby/storage/__init__.py +21 -0
  254. gobby/storage/agents.py +409 -0
  255. gobby/storage/artifact_classifier.py +341 -0
  256. gobby/storage/artifacts.py +285 -0
  257. gobby/storage/compaction.py +67 -0
  258. gobby/storage/database.py +357 -0
  259. gobby/storage/inter_session_messages.py +194 -0
  260. gobby/storage/mcp.py +680 -0
  261. gobby/storage/memories.py +562 -0
  262. gobby/storage/merge_resolutions.py +550 -0
  263. gobby/storage/migrations.py +860 -0
  264. gobby/storage/migrations_legacy.py +1359 -0
  265. gobby/storage/projects.py +166 -0
  266. gobby/storage/session_messages.py +251 -0
  267. gobby/storage/session_tasks.py +97 -0
  268. gobby/storage/sessions.py +817 -0
  269. gobby/storage/task_dependencies.py +223 -0
  270. gobby/storage/tasks/__init__.py +42 -0
  271. gobby/storage/tasks/_aggregates.py +180 -0
  272. gobby/storage/tasks/_crud.py +449 -0
  273. gobby/storage/tasks/_id.py +104 -0
  274. gobby/storage/tasks/_lifecycle.py +311 -0
  275. gobby/storage/tasks/_manager.py +889 -0
  276. gobby/storage/tasks/_models.py +300 -0
  277. gobby/storage/tasks/_ordering.py +119 -0
  278. gobby/storage/tasks/_path_cache.py +110 -0
  279. gobby/storage/tasks/_queries.py +343 -0
  280. gobby/storage/tasks/_search.py +143 -0
  281. gobby/storage/workflow_audit.py +393 -0
  282. gobby/storage/worktrees.py +547 -0
  283. gobby/sync/__init__.py +29 -0
  284. gobby/sync/github.py +333 -0
  285. gobby/sync/linear.py +304 -0
  286. gobby/sync/memories.py +284 -0
  287. gobby/sync/tasks.py +641 -0
  288. gobby/tasks/__init__.py +8 -0
  289. gobby/tasks/build_verification.py +193 -0
  290. gobby/tasks/commits.py +633 -0
  291. gobby/tasks/context.py +747 -0
  292. gobby/tasks/criteria.py +342 -0
  293. gobby/tasks/enhanced_validator.py +226 -0
  294. gobby/tasks/escalation.py +263 -0
  295. gobby/tasks/expansion.py +626 -0
  296. gobby/tasks/external_validator.py +764 -0
  297. gobby/tasks/issue_extraction.py +171 -0
  298. gobby/tasks/prompts/expand.py +327 -0
  299. gobby/tasks/research.py +421 -0
  300. gobby/tasks/tdd.py +352 -0
  301. gobby/tasks/tree_builder.py +263 -0
  302. gobby/tasks/validation.py +712 -0
  303. gobby/tasks/validation_history.py +357 -0
  304. gobby/tasks/validation_models.py +89 -0
  305. gobby/tools/__init__.py +0 -0
  306. gobby/tools/summarizer.py +170 -0
  307. gobby/tui/__init__.py +5 -0
  308. gobby/tui/api_client.py +281 -0
  309. gobby/tui/app.py +327 -0
  310. gobby/tui/screens/__init__.py +25 -0
  311. gobby/tui/screens/agents.py +333 -0
  312. gobby/tui/screens/chat.py +450 -0
  313. gobby/tui/screens/dashboard.py +377 -0
  314. gobby/tui/screens/memory.py +305 -0
  315. gobby/tui/screens/metrics.py +231 -0
  316. gobby/tui/screens/orchestrator.py +904 -0
  317. gobby/tui/screens/sessions.py +412 -0
  318. gobby/tui/screens/tasks.py +442 -0
  319. gobby/tui/screens/workflows.py +289 -0
  320. gobby/tui/screens/worktrees.py +174 -0
  321. gobby/tui/widgets/__init__.py +21 -0
  322. gobby/tui/widgets/chat.py +210 -0
  323. gobby/tui/widgets/conductor.py +104 -0
  324. gobby/tui/widgets/menu.py +132 -0
  325. gobby/tui/widgets/message_panel.py +160 -0
  326. gobby/tui/widgets/review_gate.py +224 -0
  327. gobby/tui/widgets/task_tree.py +99 -0
  328. gobby/tui/widgets/token_budget.py +166 -0
  329. gobby/tui/ws_client.py +258 -0
  330. gobby/utils/__init__.py +3 -0
  331. gobby/utils/daemon_client.py +235 -0
  332. gobby/utils/git.py +222 -0
  333. gobby/utils/id.py +38 -0
  334. gobby/utils/json_helpers.py +161 -0
  335. gobby/utils/logging.py +376 -0
  336. gobby/utils/machine_id.py +135 -0
  337. gobby/utils/metrics.py +589 -0
  338. gobby/utils/project_context.py +182 -0
  339. gobby/utils/project_init.py +263 -0
  340. gobby/utils/status.py +256 -0
  341. gobby/utils/validation.py +80 -0
  342. gobby/utils/version.py +23 -0
  343. gobby/workflows/__init__.py +4 -0
  344. gobby/workflows/actions.py +1310 -0
  345. gobby/workflows/approval_flow.py +138 -0
  346. gobby/workflows/artifact_actions.py +103 -0
  347. gobby/workflows/audit_helpers.py +110 -0
  348. gobby/workflows/autonomous_actions.py +286 -0
  349. gobby/workflows/context_actions.py +394 -0
  350. gobby/workflows/definitions.py +130 -0
  351. gobby/workflows/detection_helpers.py +208 -0
  352. gobby/workflows/engine.py +485 -0
  353. gobby/workflows/evaluator.py +669 -0
  354. gobby/workflows/git_utils.py +96 -0
  355. gobby/workflows/hooks.py +169 -0
  356. gobby/workflows/lifecycle_evaluator.py +613 -0
  357. gobby/workflows/llm_actions.py +70 -0
  358. gobby/workflows/loader.py +333 -0
  359. gobby/workflows/mcp_actions.py +60 -0
  360. gobby/workflows/memory_actions.py +272 -0
  361. gobby/workflows/premature_stop.py +164 -0
  362. gobby/workflows/session_actions.py +139 -0
  363. gobby/workflows/state_actions.py +123 -0
  364. gobby/workflows/state_manager.py +104 -0
  365. gobby/workflows/stop_signal_actions.py +163 -0
  366. gobby/workflows/summary_actions.py +344 -0
  367. gobby/workflows/task_actions.py +249 -0
  368. gobby/workflows/task_enforcement_actions.py +901 -0
  369. gobby/workflows/templates.py +52 -0
  370. gobby/workflows/todo_actions.py +84 -0
  371. gobby/workflows/webhook.py +223 -0
  372. gobby/workflows/webhook_executor.py +399 -0
  373. gobby/worktrees/__init__.py +5 -0
  374. gobby/worktrees/git.py +690 -0
  375. gobby/worktrees/merge/__init__.py +20 -0
  376. gobby/worktrees/merge/conflict_parser.py +177 -0
  377. gobby/worktrees/merge/resolver.py +485 -0
  378. gobby-0.2.5.dist-info/METADATA +351 -0
  379. gobby-0.2.5.dist-info/RECORD +383 -0
  380. gobby-0.2.5.dist-info/WHEEL +5 -0
  381. gobby-0.2.5.dist-info/entry_points.txt +2 -0
  382. gobby-0.2.5.dist-info/licenses/LICENSE.md +193 -0
  383. gobby-0.2.5.dist-info/top_level.txt +1 -0
gobby/llm/codex.py ADDED
@@ -0,0 +1,322 @@
1
+ """
2
+ Codex (OpenAI) implementation of LLMProvider.
3
+
4
+ Codex CLI supports both subscription-based (ChatGPT) and API key authentication.
5
+ After OAuth login, the CLI stores an OpenAI API key in ~/.codex/auth.json,
6
+ which can be used with the standard OpenAI Python SDK.
7
+
8
+ Auth priority:
9
+ 1. ~/.codex/auth.json (subscription mode)
10
+ 2. OPENAI_API_KEY environment variable (BYOK mode)
11
+ """
12
+
13
+ import json
14
+ import logging
15
+ import os
16
+ from pathlib import Path
17
+ from typing import Any, Literal, cast
18
+
19
+ from gobby.config.app import DaemonConfig
20
+ from gobby.llm.base import AuthMode, LLMProvider
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class CodexProvider(LLMProvider):
26
+ """
27
+ Codex (OpenAI) implementation of LLMProvider.
28
+
29
+ Supports two authentication modes:
30
+ - subscription: Read API key from ~/.codex/auth.json (after `codex login`)
31
+ - api_key: Use OPENAI_API_KEY environment variable (BYOK)
32
+ """
33
+
34
+ @property
35
+ def provider_name(self) -> str:
36
+ """Return provider name."""
37
+ return "codex"
38
+
39
+ @property
40
+ def auth_mode(self) -> AuthMode:
41
+ """Return the authentication mode for this provider."""
42
+ return self._auth_mode
43
+
44
+ def __init__(
45
+ self,
46
+ config: DaemonConfig,
47
+ auth_mode: Literal["subscription", "api_key"] | None = None,
48
+ ):
49
+ """
50
+ Initialize CodexProvider.
51
+
52
+ Args:
53
+ config: Client configuration.
54
+ auth_mode: Override auth mode. If None, reads from config.llm_providers.codex.auth_mode
55
+ or auto-detects based on available credentials.
56
+ """
57
+ self.config = config
58
+ self.logger = logger
59
+ self._client = None
60
+
61
+ # Determine auth mode from config or parameter
62
+ self._auth_mode: AuthMode = "subscription" # Default
63
+ if auth_mode:
64
+ self._auth_mode = auth_mode
65
+ elif config.llm_providers and config.llm_providers.codex:
66
+ self._auth_mode = config.llm_providers.codex.auth_mode
67
+
68
+ # Get API key based on auth mode
69
+ api_key = self._get_api_key()
70
+
71
+ if not api_key:
72
+ self.logger.warning(
73
+ "No Codex API key found. "
74
+ "Run 'codex login' for subscription mode or set OPENAI_API_KEY for BYOK."
75
+ )
76
+ return
77
+
78
+ try:
79
+ from openai import AsyncOpenAI
80
+
81
+ self._client = AsyncOpenAI(api_key=api_key)
82
+ self.logger.debug(f"Codex provider initialized (auth_mode: {self._auth_mode})")
83
+
84
+ except ImportError:
85
+ self.logger.error("OpenAI package not found. Please install with `pip install openai`.")
86
+ except Exception as e:
87
+ self.logger.error(f"Failed to initialize Codex client: {e}")
88
+
89
+ def _get_api_key(self) -> str | None:
90
+ """
91
+ Get API key based on auth mode.
92
+
93
+ For subscription mode, reads from ~/.codex/auth.json.
94
+ For api_key mode, reads from OPENAI_API_KEY environment variable.
95
+
96
+ Returns:
97
+ API key string or None if not found
98
+ """
99
+ if self._auth_mode == "subscription":
100
+ # Try to read from Codex auth.json
101
+ auth_path = Path.home() / ".codex" / "auth.json"
102
+ if auth_path.exists():
103
+ try:
104
+ with open(auth_path) as f:
105
+ auth_data = json.load(f)
106
+ api_key = auth_data.get("OPENAI_API_KEY")
107
+ if api_key:
108
+ self.logger.debug("Loaded API key from ~/.codex/auth.json")
109
+ return cast(str | None, api_key)
110
+ except Exception as e:
111
+ self.logger.warning(f"Failed to read ~/.codex/auth.json: {e}")
112
+
113
+ # Subscription mode but no auth.json - suggest login
114
+ self.logger.warning(
115
+ "Codex subscription mode but ~/.codex/auth.json not found. "
116
+ "Run 'codex login' to authenticate."
117
+ )
118
+ return None
119
+ else:
120
+ # API key mode - read from environment
121
+ env_api_key: str | None = os.environ.get("OPENAI_API_KEY")
122
+ if env_api_key:
123
+ self.logger.debug("Using OPENAI_API_KEY from environment")
124
+ return env_api_key
125
+
126
+ def _get_model(self, task: str) -> str:
127
+ """
128
+ Get the model to use for a specific task.
129
+
130
+ Args:
131
+ task: Task type ("summary" or "title")
132
+
133
+ Returns:
134
+ Model name string
135
+ """
136
+ if task == "summary":
137
+ return self.config.session_summary.model or "gpt-4o"
138
+ elif task == "title":
139
+ return self.config.title_synthesis.model or "gpt-4o-mini"
140
+ else:
141
+ return "gpt-4o"
142
+
143
+ async def generate_summary(
144
+ self, context: dict[str, Any], prompt_template: str | None = None
145
+ ) -> str:
146
+ """
147
+ Generate session summary using Codex/OpenAI.
148
+ """
149
+ if not self._client:
150
+ return "Session summary unavailable (Codex client not initialized)"
151
+
152
+ # Build formatted context for prompt template
153
+ formatted_context = {
154
+ "transcript_summary": context.get("transcript_summary", ""),
155
+ "last_messages": json.dumps(context.get("last_messages", []), indent=2),
156
+ "git_status": context.get("git_status", ""),
157
+ "file_changes": context.get("file_changes", ""),
158
+ **{
159
+ k: v
160
+ for k, v in context.items()
161
+ if k not in ["transcript_summary", "last_messages", "git_status", "file_changes"]
162
+ },
163
+ }
164
+
165
+ # Build prompt - prompt_template is required
166
+ if not prompt_template:
167
+ raise ValueError(
168
+ "prompt_template is required for generate_summary. "
169
+ "Configure 'session_summary.prompt' in ~/.gobby/config.yaml"
170
+ )
171
+ prompt = prompt_template.format(**formatted_context)
172
+
173
+ try:
174
+ response = await self._client.chat.completions.create(
175
+ model=self._get_model("summary"),
176
+ messages=[
177
+ {
178
+ "role": "system",
179
+ "content": "You are a session summary generator. Create comprehensive, actionable summaries.",
180
+ },
181
+ {"role": "user", "content": prompt},
182
+ ],
183
+ max_tokens=4000,
184
+ )
185
+ return response.choices[0].message.content or ""
186
+ except Exception as e:
187
+ self.logger.error(f"Failed to generate summary with Codex: {e}")
188
+ return f"Session summary generation failed: {e}"
189
+
190
+ async def synthesize_title(
191
+ self, user_prompt: str, prompt_template: str | None = None
192
+ ) -> str | None:
193
+ """
194
+ Synthesize session title using Codex/OpenAI.
195
+ """
196
+ if not self._client:
197
+ return None
198
+
199
+ # Build prompt - prompt_template is required
200
+ if not prompt_template:
201
+ raise ValueError(
202
+ "prompt_template is required for synthesize_title. "
203
+ "Configure 'title_synthesis.prompt' in ~/.gobby/config.yaml"
204
+ )
205
+ prompt = prompt_template.format(user_prompt=user_prompt)
206
+
207
+ try:
208
+ response = await self._client.chat.completions.create(
209
+ model=self._get_model("title"),
210
+ messages=[
211
+ {
212
+ "role": "system",
213
+ "content": "You are a session title generator. Create concise, descriptive titles.",
214
+ },
215
+ {"role": "user", "content": prompt},
216
+ ],
217
+ max_tokens=50,
218
+ )
219
+ return (response.choices[0].message.content or "").strip()
220
+ except Exception as e:
221
+ self.logger.error(f"Failed to synthesize title with Codex: {e}")
222
+ return None
223
+
224
+ async def generate_text(
225
+ self,
226
+ prompt: str,
227
+ system_prompt: str | None = None,
228
+ model: str | None = None,
229
+ ) -> str:
230
+ """
231
+ Generate text using Codex/OpenAI.
232
+ """
233
+ if not self._client:
234
+ return "Generation unavailable (Codex client not initialized)"
235
+
236
+ try:
237
+ response = await self._client.chat.completions.create(
238
+ model=model or "gpt-4o",
239
+ messages=[
240
+ {
241
+ "role": "system",
242
+ "content": system_prompt or "You are a helpful assistant.",
243
+ },
244
+ {"role": "user", "content": prompt},
245
+ ],
246
+ max_tokens=4000,
247
+ )
248
+ return response.choices[0].message.content or ""
249
+ except Exception as e:
250
+ self.logger.error(f"Failed to generate text with Codex: {e}")
251
+ return f"Generation failed: {e}"
252
+
253
+ async def describe_image(
254
+ self,
255
+ image_path: str,
256
+ context: str | None = None,
257
+ ) -> str:
258
+ """
259
+ Generate a text description of an image using OpenAI's vision capabilities.
260
+
261
+ Uses GPT-4o for vision tasks.
262
+
263
+ Args:
264
+ image_path: Path to the image file
265
+ context: Optional context to guide the description
266
+
267
+ Returns:
268
+ Text description of the image
269
+ """
270
+ import base64
271
+ import mimetypes
272
+
273
+ if not self._client:
274
+ return "Image description unavailable (Codex client not initialized)"
275
+
276
+ path = Path(image_path)
277
+ if not path.exists():
278
+ return f"Image not found: {image_path}"
279
+
280
+ try:
281
+ # Read and encode image
282
+ image_data = path.read_bytes()
283
+ image_base64 = base64.standard_b64encode(image_data).decode("utf-8")
284
+
285
+ # Determine MIME type
286
+ mime_type, _ = mimetypes.guess_type(str(path))
287
+ if mime_type not in ["image/jpeg", "image/png", "image/gif", "image/webp"]:
288
+ mime_type = "image/png" # Default to PNG
289
+
290
+ # Build prompt
291
+ prompt = (
292
+ "Please describe this image in detail, focusing on key visual elements, "
293
+ "any text visible, and the overall context or meaning."
294
+ )
295
+ if context:
296
+ prompt = f"{context}\n\n{prompt}"
297
+
298
+ # Use GPT-4o for vision
299
+ response = await self._client.chat.completions.create(
300
+ model="gpt-4o",
301
+ messages=[
302
+ {
303
+ "role": "user",
304
+ "content": [
305
+ {
306
+ "type": "image_url",
307
+ "image_url": {
308
+ "url": f"data:{mime_type};base64,{image_base64}",
309
+ },
310
+ },
311
+ {"type": "text", "text": prompt},
312
+ ],
313
+ }
314
+ ],
315
+ max_tokens=1024,
316
+ )
317
+
318
+ return response.choices[0].message.content or "No description generated"
319
+
320
+ except Exception as e:
321
+ self.logger.error(f"Failed to describe image with Codex: {e}")
322
+ return f"Image description failed: {e}"