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.
- gobby/__init__.py +3 -0
- gobby/adapters/__init__.py +30 -0
- gobby/adapters/base.py +93 -0
- gobby/adapters/claude_code.py +276 -0
- gobby/adapters/codex.py +1292 -0
- gobby/adapters/gemini.py +343 -0
- gobby/agents/__init__.py +37 -0
- gobby/agents/codex_session.py +120 -0
- gobby/agents/constants.py +112 -0
- gobby/agents/context.py +362 -0
- gobby/agents/definitions.py +133 -0
- gobby/agents/gemini_session.py +111 -0
- gobby/agents/registry.py +618 -0
- gobby/agents/runner.py +968 -0
- gobby/agents/session.py +259 -0
- gobby/agents/spawn.py +916 -0
- gobby/agents/spawners/__init__.py +77 -0
- gobby/agents/spawners/base.py +142 -0
- gobby/agents/spawners/cross_platform.py +266 -0
- gobby/agents/spawners/embedded.py +225 -0
- gobby/agents/spawners/headless.py +226 -0
- gobby/agents/spawners/linux.py +125 -0
- gobby/agents/spawners/macos.py +277 -0
- gobby/agents/spawners/windows.py +308 -0
- gobby/agents/tty_config.py +319 -0
- gobby/autonomous/__init__.py +32 -0
- gobby/autonomous/progress_tracker.py +447 -0
- gobby/autonomous/stop_registry.py +269 -0
- gobby/autonomous/stuck_detector.py +383 -0
- gobby/cli/__init__.py +67 -0
- gobby/cli/__main__.py +8 -0
- gobby/cli/agents.py +529 -0
- gobby/cli/artifacts.py +266 -0
- gobby/cli/daemon.py +329 -0
- gobby/cli/extensions.py +526 -0
- gobby/cli/github.py +263 -0
- gobby/cli/init.py +53 -0
- gobby/cli/install.py +614 -0
- gobby/cli/installers/__init__.py +37 -0
- gobby/cli/installers/antigravity.py +65 -0
- gobby/cli/installers/claude.py +363 -0
- gobby/cli/installers/codex.py +192 -0
- gobby/cli/installers/gemini.py +294 -0
- gobby/cli/installers/git_hooks.py +377 -0
- gobby/cli/installers/shared.py +737 -0
- gobby/cli/linear.py +250 -0
- gobby/cli/mcp.py +30 -0
- gobby/cli/mcp_proxy.py +698 -0
- gobby/cli/memory.py +304 -0
- gobby/cli/merge.py +384 -0
- gobby/cli/projects.py +79 -0
- gobby/cli/sessions.py +622 -0
- gobby/cli/tasks/__init__.py +30 -0
- gobby/cli/tasks/_utils.py +658 -0
- gobby/cli/tasks/ai.py +1025 -0
- gobby/cli/tasks/commits.py +169 -0
- gobby/cli/tasks/crud.py +685 -0
- gobby/cli/tasks/deps.py +135 -0
- gobby/cli/tasks/labels.py +63 -0
- gobby/cli/tasks/main.py +273 -0
- gobby/cli/tasks/search.py +178 -0
- gobby/cli/tui.py +34 -0
- gobby/cli/utils.py +513 -0
- gobby/cli/workflows.py +927 -0
- gobby/cli/worktrees.py +481 -0
- gobby/config/__init__.py +129 -0
- gobby/config/app.py +551 -0
- gobby/config/extensions.py +167 -0
- gobby/config/features.py +472 -0
- gobby/config/llm_providers.py +98 -0
- gobby/config/logging.py +66 -0
- gobby/config/mcp.py +346 -0
- gobby/config/persistence.py +247 -0
- gobby/config/servers.py +141 -0
- gobby/config/sessions.py +250 -0
- gobby/config/tasks.py +784 -0
- gobby/hooks/__init__.py +104 -0
- gobby/hooks/artifact_capture.py +213 -0
- gobby/hooks/broadcaster.py +243 -0
- gobby/hooks/event_handlers.py +723 -0
- gobby/hooks/events.py +218 -0
- gobby/hooks/git.py +169 -0
- gobby/hooks/health_monitor.py +171 -0
- gobby/hooks/hook_manager.py +856 -0
- gobby/hooks/hook_types.py +575 -0
- gobby/hooks/plugins.py +813 -0
- gobby/hooks/session_coordinator.py +396 -0
- gobby/hooks/verification_runner.py +268 -0
- gobby/hooks/webhooks.py +339 -0
- gobby/install/claude/commands/gobby/bug.md +51 -0
- gobby/install/claude/commands/gobby/chore.md +51 -0
- gobby/install/claude/commands/gobby/epic.md +52 -0
- gobby/install/claude/commands/gobby/eval.md +235 -0
- gobby/install/claude/commands/gobby/feat.md +49 -0
- gobby/install/claude/commands/gobby/nit.md +52 -0
- gobby/install/claude/commands/gobby/ref.md +52 -0
- gobby/install/claude/hooks/HOOK_SCHEMAS.md +632 -0
- gobby/install/claude/hooks/hook_dispatcher.py +364 -0
- gobby/install/claude/hooks/validate_settings.py +102 -0
- gobby/install/claude/hooks-template.json +118 -0
- gobby/install/codex/hooks/hook_dispatcher.py +153 -0
- gobby/install/codex/prompts/forget.md +7 -0
- gobby/install/codex/prompts/memories.md +7 -0
- gobby/install/codex/prompts/recall.md +7 -0
- gobby/install/codex/prompts/remember.md +13 -0
- gobby/install/gemini/hooks/hook_dispatcher.py +268 -0
- gobby/install/gemini/hooks-template.json +138 -0
- gobby/install/shared/plugins/code_guardian.py +456 -0
- gobby/install/shared/plugins/example_notify.py +331 -0
- gobby/integrations/__init__.py +10 -0
- gobby/integrations/github.py +145 -0
- gobby/integrations/linear.py +145 -0
- gobby/llm/__init__.py +40 -0
- gobby/llm/base.py +120 -0
- gobby/llm/claude.py +578 -0
- gobby/llm/claude_executor.py +503 -0
- gobby/llm/codex.py +322 -0
- gobby/llm/codex_executor.py +513 -0
- gobby/llm/executor.py +316 -0
- gobby/llm/factory.py +34 -0
- gobby/llm/gemini.py +258 -0
- gobby/llm/gemini_executor.py +339 -0
- gobby/llm/litellm.py +287 -0
- gobby/llm/litellm_executor.py +303 -0
- gobby/llm/resolver.py +499 -0
- gobby/llm/service.py +236 -0
- gobby/mcp_proxy/__init__.py +29 -0
- gobby/mcp_proxy/actions.py +175 -0
- gobby/mcp_proxy/daemon_control.py +198 -0
- gobby/mcp_proxy/importer.py +436 -0
- gobby/mcp_proxy/lazy.py +325 -0
- gobby/mcp_proxy/manager.py +798 -0
- gobby/mcp_proxy/metrics.py +609 -0
- gobby/mcp_proxy/models.py +139 -0
- gobby/mcp_proxy/registries.py +215 -0
- gobby/mcp_proxy/schema_hash.py +381 -0
- gobby/mcp_proxy/semantic_search.py +706 -0
- gobby/mcp_proxy/server.py +549 -0
- gobby/mcp_proxy/services/__init__.py +0 -0
- gobby/mcp_proxy/services/fallback.py +306 -0
- gobby/mcp_proxy/services/recommendation.py +224 -0
- gobby/mcp_proxy/services/server_mgmt.py +214 -0
- gobby/mcp_proxy/services/system.py +72 -0
- gobby/mcp_proxy/services/tool_filter.py +231 -0
- gobby/mcp_proxy/services/tool_proxy.py +309 -0
- gobby/mcp_proxy/stdio.py +565 -0
- gobby/mcp_proxy/tools/__init__.py +27 -0
- gobby/mcp_proxy/tools/agents.py +1103 -0
- gobby/mcp_proxy/tools/artifacts.py +207 -0
- gobby/mcp_proxy/tools/hub.py +335 -0
- gobby/mcp_proxy/tools/internal.py +337 -0
- gobby/mcp_proxy/tools/memory.py +543 -0
- gobby/mcp_proxy/tools/merge.py +422 -0
- gobby/mcp_proxy/tools/metrics.py +283 -0
- gobby/mcp_proxy/tools/orchestration/__init__.py +23 -0
- gobby/mcp_proxy/tools/orchestration/cleanup.py +619 -0
- gobby/mcp_proxy/tools/orchestration/monitor.py +380 -0
- gobby/mcp_proxy/tools/orchestration/orchestrate.py +746 -0
- gobby/mcp_proxy/tools/orchestration/review.py +736 -0
- gobby/mcp_proxy/tools/orchestration/utils.py +16 -0
- gobby/mcp_proxy/tools/session_messages.py +1056 -0
- gobby/mcp_proxy/tools/task_dependencies.py +219 -0
- gobby/mcp_proxy/tools/task_expansion.py +591 -0
- gobby/mcp_proxy/tools/task_github.py +393 -0
- gobby/mcp_proxy/tools/task_linear.py +379 -0
- gobby/mcp_proxy/tools/task_orchestration.py +77 -0
- gobby/mcp_proxy/tools/task_readiness.py +522 -0
- gobby/mcp_proxy/tools/task_sync.py +351 -0
- gobby/mcp_proxy/tools/task_validation.py +843 -0
- gobby/mcp_proxy/tools/tasks/__init__.py +25 -0
- gobby/mcp_proxy/tools/tasks/_context.py +112 -0
- gobby/mcp_proxy/tools/tasks/_crud.py +516 -0
- gobby/mcp_proxy/tools/tasks/_factory.py +176 -0
- gobby/mcp_proxy/tools/tasks/_helpers.py +129 -0
- gobby/mcp_proxy/tools/tasks/_lifecycle.py +517 -0
- gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +301 -0
- gobby/mcp_proxy/tools/tasks/_resolution.py +55 -0
- gobby/mcp_proxy/tools/tasks/_search.py +215 -0
- gobby/mcp_proxy/tools/tasks/_session.py +125 -0
- gobby/mcp_proxy/tools/workflows.py +973 -0
- gobby/mcp_proxy/tools/worktrees.py +1264 -0
- gobby/mcp_proxy/transports/__init__.py +0 -0
- gobby/mcp_proxy/transports/base.py +95 -0
- gobby/mcp_proxy/transports/factory.py +44 -0
- gobby/mcp_proxy/transports/http.py +139 -0
- gobby/mcp_proxy/transports/stdio.py +213 -0
- gobby/mcp_proxy/transports/websocket.py +136 -0
- gobby/memory/backends/__init__.py +116 -0
- gobby/memory/backends/mem0.py +408 -0
- gobby/memory/backends/memu.py +485 -0
- gobby/memory/backends/null.py +111 -0
- gobby/memory/backends/openmemory.py +537 -0
- gobby/memory/backends/sqlite.py +304 -0
- gobby/memory/context.py +87 -0
- gobby/memory/manager.py +1001 -0
- gobby/memory/protocol.py +451 -0
- gobby/memory/search/__init__.py +66 -0
- gobby/memory/search/text.py +127 -0
- gobby/memory/viz.py +258 -0
- gobby/prompts/__init__.py +13 -0
- gobby/prompts/defaults/expansion/system.md +119 -0
- gobby/prompts/defaults/expansion/user.md +48 -0
- gobby/prompts/defaults/external_validation/agent.md +72 -0
- gobby/prompts/defaults/external_validation/external.md +63 -0
- gobby/prompts/defaults/external_validation/spawn.md +83 -0
- gobby/prompts/defaults/external_validation/system.md +6 -0
- gobby/prompts/defaults/features/import_mcp.md +22 -0
- gobby/prompts/defaults/features/import_mcp_github.md +17 -0
- gobby/prompts/defaults/features/import_mcp_search.md +16 -0
- gobby/prompts/defaults/features/recommend_tools.md +32 -0
- gobby/prompts/defaults/features/recommend_tools_hybrid.md +35 -0
- gobby/prompts/defaults/features/recommend_tools_llm.md +30 -0
- gobby/prompts/defaults/features/server_description.md +20 -0
- gobby/prompts/defaults/features/server_description_system.md +6 -0
- gobby/prompts/defaults/features/task_description.md +31 -0
- gobby/prompts/defaults/features/task_description_system.md +6 -0
- gobby/prompts/defaults/features/tool_summary.md +17 -0
- gobby/prompts/defaults/features/tool_summary_system.md +6 -0
- gobby/prompts/defaults/research/step.md +58 -0
- gobby/prompts/defaults/validation/criteria.md +47 -0
- gobby/prompts/defaults/validation/validate.md +38 -0
- gobby/prompts/loader.py +346 -0
- gobby/prompts/models.py +113 -0
- gobby/py.typed +0 -0
- gobby/runner.py +488 -0
- gobby/search/__init__.py +23 -0
- gobby/search/protocol.py +104 -0
- gobby/search/tfidf.py +232 -0
- gobby/servers/__init__.py +7 -0
- gobby/servers/http.py +636 -0
- gobby/servers/models.py +31 -0
- gobby/servers/routes/__init__.py +23 -0
- gobby/servers/routes/admin.py +416 -0
- gobby/servers/routes/dependencies.py +118 -0
- gobby/servers/routes/mcp/__init__.py +24 -0
- gobby/servers/routes/mcp/hooks.py +135 -0
- gobby/servers/routes/mcp/plugins.py +121 -0
- gobby/servers/routes/mcp/tools.py +1337 -0
- gobby/servers/routes/mcp/webhooks.py +159 -0
- gobby/servers/routes/sessions.py +582 -0
- gobby/servers/websocket.py +766 -0
- gobby/sessions/__init__.py +13 -0
- gobby/sessions/analyzer.py +322 -0
- gobby/sessions/lifecycle.py +240 -0
- gobby/sessions/manager.py +563 -0
- gobby/sessions/processor.py +225 -0
- gobby/sessions/summary.py +532 -0
- gobby/sessions/transcripts/__init__.py +41 -0
- gobby/sessions/transcripts/base.py +125 -0
- gobby/sessions/transcripts/claude.py +386 -0
- gobby/sessions/transcripts/codex.py +143 -0
- gobby/sessions/transcripts/gemini.py +195 -0
- gobby/storage/__init__.py +21 -0
- gobby/storage/agents.py +409 -0
- gobby/storage/artifact_classifier.py +341 -0
- gobby/storage/artifacts.py +285 -0
- gobby/storage/compaction.py +67 -0
- gobby/storage/database.py +357 -0
- gobby/storage/inter_session_messages.py +194 -0
- gobby/storage/mcp.py +680 -0
- gobby/storage/memories.py +562 -0
- gobby/storage/merge_resolutions.py +550 -0
- gobby/storage/migrations.py +860 -0
- gobby/storage/migrations_legacy.py +1359 -0
- gobby/storage/projects.py +166 -0
- gobby/storage/session_messages.py +251 -0
- gobby/storage/session_tasks.py +97 -0
- gobby/storage/sessions.py +817 -0
- gobby/storage/task_dependencies.py +223 -0
- gobby/storage/tasks/__init__.py +42 -0
- gobby/storage/tasks/_aggregates.py +180 -0
- gobby/storage/tasks/_crud.py +449 -0
- gobby/storage/tasks/_id.py +104 -0
- gobby/storage/tasks/_lifecycle.py +311 -0
- gobby/storage/tasks/_manager.py +889 -0
- gobby/storage/tasks/_models.py +300 -0
- gobby/storage/tasks/_ordering.py +119 -0
- gobby/storage/tasks/_path_cache.py +110 -0
- gobby/storage/tasks/_queries.py +343 -0
- gobby/storage/tasks/_search.py +143 -0
- gobby/storage/workflow_audit.py +393 -0
- gobby/storage/worktrees.py +547 -0
- gobby/sync/__init__.py +29 -0
- gobby/sync/github.py +333 -0
- gobby/sync/linear.py +304 -0
- gobby/sync/memories.py +284 -0
- gobby/sync/tasks.py +641 -0
- gobby/tasks/__init__.py +8 -0
- gobby/tasks/build_verification.py +193 -0
- gobby/tasks/commits.py +633 -0
- gobby/tasks/context.py +747 -0
- gobby/tasks/criteria.py +342 -0
- gobby/tasks/enhanced_validator.py +226 -0
- gobby/tasks/escalation.py +263 -0
- gobby/tasks/expansion.py +626 -0
- gobby/tasks/external_validator.py +764 -0
- gobby/tasks/issue_extraction.py +171 -0
- gobby/tasks/prompts/expand.py +327 -0
- gobby/tasks/research.py +421 -0
- gobby/tasks/tdd.py +352 -0
- gobby/tasks/tree_builder.py +263 -0
- gobby/tasks/validation.py +712 -0
- gobby/tasks/validation_history.py +357 -0
- gobby/tasks/validation_models.py +89 -0
- gobby/tools/__init__.py +0 -0
- gobby/tools/summarizer.py +170 -0
- gobby/tui/__init__.py +5 -0
- gobby/tui/api_client.py +281 -0
- gobby/tui/app.py +327 -0
- gobby/tui/screens/__init__.py +25 -0
- gobby/tui/screens/agents.py +333 -0
- gobby/tui/screens/chat.py +450 -0
- gobby/tui/screens/dashboard.py +377 -0
- gobby/tui/screens/memory.py +305 -0
- gobby/tui/screens/metrics.py +231 -0
- gobby/tui/screens/orchestrator.py +904 -0
- gobby/tui/screens/sessions.py +412 -0
- gobby/tui/screens/tasks.py +442 -0
- gobby/tui/screens/workflows.py +289 -0
- gobby/tui/screens/worktrees.py +174 -0
- gobby/tui/widgets/__init__.py +21 -0
- gobby/tui/widgets/chat.py +210 -0
- gobby/tui/widgets/conductor.py +104 -0
- gobby/tui/widgets/menu.py +132 -0
- gobby/tui/widgets/message_panel.py +160 -0
- gobby/tui/widgets/review_gate.py +224 -0
- gobby/tui/widgets/task_tree.py +99 -0
- gobby/tui/widgets/token_budget.py +166 -0
- gobby/tui/ws_client.py +258 -0
- gobby/utils/__init__.py +3 -0
- gobby/utils/daemon_client.py +235 -0
- gobby/utils/git.py +222 -0
- gobby/utils/id.py +38 -0
- gobby/utils/json_helpers.py +161 -0
- gobby/utils/logging.py +376 -0
- gobby/utils/machine_id.py +135 -0
- gobby/utils/metrics.py +589 -0
- gobby/utils/project_context.py +182 -0
- gobby/utils/project_init.py +263 -0
- gobby/utils/status.py +256 -0
- gobby/utils/validation.py +80 -0
- gobby/utils/version.py +23 -0
- gobby/workflows/__init__.py +4 -0
- gobby/workflows/actions.py +1310 -0
- gobby/workflows/approval_flow.py +138 -0
- gobby/workflows/artifact_actions.py +103 -0
- gobby/workflows/audit_helpers.py +110 -0
- gobby/workflows/autonomous_actions.py +286 -0
- gobby/workflows/context_actions.py +394 -0
- gobby/workflows/definitions.py +130 -0
- gobby/workflows/detection_helpers.py +208 -0
- gobby/workflows/engine.py +485 -0
- gobby/workflows/evaluator.py +669 -0
- gobby/workflows/git_utils.py +96 -0
- gobby/workflows/hooks.py +169 -0
- gobby/workflows/lifecycle_evaluator.py +613 -0
- gobby/workflows/llm_actions.py +70 -0
- gobby/workflows/loader.py +333 -0
- gobby/workflows/mcp_actions.py +60 -0
- gobby/workflows/memory_actions.py +272 -0
- gobby/workflows/premature_stop.py +164 -0
- gobby/workflows/session_actions.py +139 -0
- gobby/workflows/state_actions.py +123 -0
- gobby/workflows/state_manager.py +104 -0
- gobby/workflows/stop_signal_actions.py +163 -0
- gobby/workflows/summary_actions.py +344 -0
- gobby/workflows/task_actions.py +249 -0
- gobby/workflows/task_enforcement_actions.py +901 -0
- gobby/workflows/templates.py +52 -0
- gobby/workflows/todo_actions.py +84 -0
- gobby/workflows/webhook.py +223 -0
- gobby/workflows/webhook_executor.py +399 -0
- gobby/worktrees/__init__.py +5 -0
- gobby/worktrees/git.py +690 -0
- gobby/worktrees/merge/__init__.py +20 -0
- gobby/worktrees/merge/conflict_parser.py +177 -0
- gobby/worktrees/merge/resolver.py +485 -0
- gobby-0.2.5.dist-info/METADATA +351 -0
- gobby-0.2.5.dist-info/RECORD +383 -0
- gobby-0.2.5.dist-info/WHEEL +5 -0
- gobby-0.2.5.dist-info/entry_points.txt +2 -0
- gobby-0.2.5.dist-info/licenses/LICENSE.md +193 -0
- 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}"
|