gobby 0.2.5__py3-none-any.whl → 0.2.7__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 +1 -1
- gobby/adapters/__init__.py +2 -1
- gobby/adapters/claude_code.py +13 -4
- gobby/adapters/codex_impl/__init__.py +28 -0
- gobby/adapters/codex_impl/adapter.py +722 -0
- gobby/adapters/codex_impl/client.py +679 -0
- gobby/adapters/codex_impl/protocol.py +20 -0
- gobby/adapters/codex_impl/types.py +68 -0
- gobby/agents/definitions.py +11 -1
- gobby/agents/isolation.py +395 -0
- gobby/agents/runner.py +8 -0
- gobby/agents/sandbox.py +261 -0
- gobby/agents/spawn.py +42 -287
- gobby/agents/spawn_executor.py +385 -0
- gobby/agents/spawners/__init__.py +24 -0
- gobby/agents/spawners/command_builder.py +189 -0
- gobby/agents/spawners/embedded.py +21 -2
- gobby/agents/spawners/headless.py +21 -2
- gobby/agents/spawners/prompt_manager.py +125 -0
- gobby/cli/__init__.py +6 -0
- gobby/cli/clones.py +419 -0
- gobby/cli/conductor.py +266 -0
- gobby/cli/install.py +4 -4
- gobby/cli/installers/antigravity.py +3 -9
- gobby/cli/installers/claude.py +15 -9
- gobby/cli/installers/codex.py +2 -8
- gobby/cli/installers/gemini.py +8 -8
- gobby/cli/installers/shared.py +175 -13
- gobby/cli/sessions.py +1 -1
- gobby/cli/skills.py +858 -0
- gobby/cli/tasks/ai.py +0 -440
- gobby/cli/tasks/crud.py +44 -6
- gobby/cli/tasks/main.py +0 -4
- gobby/cli/tui.py +2 -2
- gobby/cli/utils.py +12 -5
- gobby/clones/__init__.py +13 -0
- gobby/clones/git.py +547 -0
- gobby/conductor/__init__.py +16 -0
- gobby/conductor/alerts.py +135 -0
- gobby/conductor/loop.py +164 -0
- gobby/conductor/monitors/__init__.py +11 -0
- gobby/conductor/monitors/agents.py +116 -0
- gobby/conductor/monitors/tasks.py +155 -0
- gobby/conductor/pricing.py +234 -0
- gobby/conductor/token_tracker.py +160 -0
- gobby/config/__init__.py +12 -97
- gobby/config/app.py +69 -91
- gobby/config/extensions.py +2 -2
- gobby/config/features.py +7 -130
- gobby/config/search.py +110 -0
- gobby/config/servers.py +1 -1
- gobby/config/skills.py +43 -0
- gobby/config/tasks.py +9 -41
- gobby/hooks/__init__.py +0 -13
- gobby/hooks/event_handlers.py +188 -2
- gobby/hooks/hook_manager.py +50 -4
- gobby/hooks/plugins.py +1 -1
- gobby/hooks/skill_manager.py +130 -0
- gobby/hooks/webhooks.py +1 -1
- gobby/install/claude/hooks/hook_dispatcher.py +4 -4
- gobby/install/codex/hooks/hook_dispatcher.py +1 -1
- gobby/install/gemini/hooks/hook_dispatcher.py +87 -12
- gobby/llm/claude.py +22 -34
- gobby/llm/claude_executor.py +46 -256
- gobby/llm/codex_executor.py +59 -291
- gobby/llm/executor.py +21 -0
- gobby/llm/gemini.py +134 -110
- gobby/llm/litellm_executor.py +143 -6
- gobby/llm/resolver.py +98 -35
- gobby/mcp_proxy/importer.py +62 -4
- gobby/mcp_proxy/instructions.py +56 -0
- gobby/mcp_proxy/models.py +15 -0
- gobby/mcp_proxy/registries.py +68 -8
- gobby/mcp_proxy/server.py +33 -3
- gobby/mcp_proxy/services/recommendation.py +43 -11
- gobby/mcp_proxy/services/tool_proxy.py +81 -1
- gobby/mcp_proxy/stdio.py +2 -1
- gobby/mcp_proxy/tools/__init__.py +0 -2
- gobby/mcp_proxy/tools/agent_messaging.py +317 -0
- gobby/mcp_proxy/tools/agents.py +31 -731
- gobby/mcp_proxy/tools/clones.py +518 -0
- gobby/mcp_proxy/tools/memory.py +3 -26
- gobby/mcp_proxy/tools/metrics.py +65 -1
- gobby/mcp_proxy/tools/orchestration/__init__.py +3 -0
- gobby/mcp_proxy/tools/orchestration/cleanup.py +151 -0
- gobby/mcp_proxy/tools/orchestration/wait.py +467 -0
- gobby/mcp_proxy/tools/sessions/__init__.py +14 -0
- gobby/mcp_proxy/tools/sessions/_commits.py +232 -0
- gobby/mcp_proxy/tools/sessions/_crud.py +253 -0
- gobby/mcp_proxy/tools/sessions/_factory.py +63 -0
- gobby/mcp_proxy/tools/sessions/_handoff.py +499 -0
- gobby/mcp_proxy/tools/sessions/_messages.py +138 -0
- gobby/mcp_proxy/tools/skills/__init__.py +616 -0
- gobby/mcp_proxy/tools/spawn_agent.py +417 -0
- gobby/mcp_proxy/tools/task_orchestration.py +7 -0
- gobby/mcp_proxy/tools/task_readiness.py +14 -0
- gobby/mcp_proxy/tools/task_sync.py +1 -1
- gobby/mcp_proxy/tools/tasks/_context.py +0 -20
- gobby/mcp_proxy/tools/tasks/_crud.py +91 -4
- gobby/mcp_proxy/tools/tasks/_expansion.py +348 -0
- gobby/mcp_proxy/tools/tasks/_factory.py +6 -16
- gobby/mcp_proxy/tools/tasks/_lifecycle.py +110 -45
- gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +18 -29
- gobby/mcp_proxy/tools/workflows.py +1 -1
- gobby/mcp_proxy/tools/worktrees.py +0 -338
- gobby/memory/backends/__init__.py +6 -1
- gobby/memory/backends/mem0.py +6 -1
- gobby/memory/extractor.py +477 -0
- gobby/memory/ingestion/__init__.py +5 -0
- gobby/memory/ingestion/multimodal.py +221 -0
- gobby/memory/manager.py +73 -285
- gobby/memory/search/__init__.py +10 -0
- gobby/memory/search/coordinator.py +248 -0
- gobby/memory/services/__init__.py +5 -0
- gobby/memory/services/crossref.py +142 -0
- gobby/prompts/loader.py +5 -2
- gobby/runner.py +37 -16
- gobby/search/__init__.py +48 -6
- gobby/search/backends/__init__.py +159 -0
- gobby/search/backends/embedding.py +225 -0
- gobby/search/embeddings.py +238 -0
- gobby/search/models.py +148 -0
- gobby/search/unified.py +496 -0
- gobby/servers/http.py +24 -12
- gobby/servers/routes/admin.py +294 -0
- gobby/servers/routes/mcp/endpoints/__init__.py +61 -0
- gobby/servers/routes/mcp/endpoints/discovery.py +405 -0
- gobby/servers/routes/mcp/endpoints/execution.py +568 -0
- gobby/servers/routes/mcp/endpoints/registry.py +378 -0
- gobby/servers/routes/mcp/endpoints/server.py +304 -0
- gobby/servers/routes/mcp/hooks.py +1 -1
- gobby/servers/routes/mcp/tools.py +48 -1317
- gobby/servers/websocket.py +2 -2
- gobby/sessions/analyzer.py +2 -0
- gobby/sessions/lifecycle.py +1 -1
- gobby/sessions/processor.py +10 -0
- gobby/sessions/transcripts/base.py +2 -0
- gobby/sessions/transcripts/claude.py +79 -10
- gobby/skills/__init__.py +91 -0
- gobby/skills/loader.py +685 -0
- gobby/skills/manager.py +384 -0
- gobby/skills/parser.py +286 -0
- gobby/skills/search.py +463 -0
- gobby/skills/sync.py +119 -0
- gobby/skills/updater.py +385 -0
- gobby/skills/validator.py +368 -0
- gobby/storage/clones.py +378 -0
- gobby/storage/database.py +1 -1
- gobby/storage/memories.py +43 -13
- gobby/storage/migrations.py +162 -201
- gobby/storage/sessions.py +116 -7
- gobby/storage/skills.py +782 -0
- gobby/storage/tasks/_crud.py +4 -4
- gobby/storage/tasks/_lifecycle.py +57 -7
- gobby/storage/tasks/_manager.py +14 -5
- gobby/storage/tasks/_models.py +8 -3
- gobby/sync/memories.py +40 -5
- gobby/sync/tasks.py +83 -6
- gobby/tasks/__init__.py +1 -2
- gobby/tasks/external_validator.py +1 -1
- gobby/tasks/validation.py +46 -35
- gobby/tools/summarizer.py +91 -10
- gobby/tui/api_client.py +4 -7
- gobby/tui/app.py +5 -3
- gobby/tui/screens/orchestrator.py +1 -2
- gobby/tui/screens/tasks.py +2 -4
- gobby/tui/ws_client.py +1 -1
- gobby/utils/daemon_client.py +2 -2
- gobby/utils/project_context.py +2 -3
- gobby/utils/status.py +13 -0
- gobby/workflows/actions.py +221 -1135
- gobby/workflows/artifact_actions.py +31 -0
- gobby/workflows/autonomous_actions.py +11 -0
- gobby/workflows/context_actions.py +93 -1
- gobby/workflows/detection_helpers.py +115 -31
- gobby/workflows/enforcement/__init__.py +47 -0
- gobby/workflows/enforcement/blocking.py +269 -0
- gobby/workflows/enforcement/commit_policy.py +283 -0
- gobby/workflows/enforcement/handlers.py +269 -0
- gobby/workflows/{task_enforcement_actions.py → enforcement/task_policy.py} +29 -388
- gobby/workflows/engine.py +13 -2
- gobby/workflows/git_utils.py +106 -0
- gobby/workflows/lifecycle_evaluator.py +29 -1
- gobby/workflows/llm_actions.py +30 -0
- gobby/workflows/loader.py +19 -6
- gobby/workflows/mcp_actions.py +20 -1
- gobby/workflows/memory_actions.py +154 -0
- gobby/workflows/safe_evaluator.py +183 -0
- gobby/workflows/session_actions.py +44 -0
- gobby/workflows/state_actions.py +60 -1
- gobby/workflows/stop_signal_actions.py +55 -0
- gobby/workflows/summary_actions.py +111 -1
- gobby/workflows/task_sync_actions.py +347 -0
- gobby/workflows/todo_actions.py +34 -1
- gobby/workflows/webhook_actions.py +185 -0
- {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/METADATA +87 -21
- {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/RECORD +201 -172
- {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/WHEEL +1 -1
- gobby/adapters/codex.py +0 -1292
- gobby/install/claude/commands/gobby/bug.md +0 -51
- gobby/install/claude/commands/gobby/chore.md +0 -51
- gobby/install/claude/commands/gobby/epic.md +0 -52
- gobby/install/claude/commands/gobby/eval.md +0 -235
- gobby/install/claude/commands/gobby/feat.md +0 -49
- gobby/install/claude/commands/gobby/nit.md +0 -52
- gobby/install/claude/commands/gobby/ref.md +0 -52
- gobby/install/codex/prompts/forget.md +0 -7
- gobby/install/codex/prompts/memories.md +0 -7
- gobby/install/codex/prompts/recall.md +0 -7
- gobby/install/codex/prompts/remember.md +0 -13
- gobby/llm/gemini_executor.py +0 -339
- gobby/mcp_proxy/tools/session_messages.py +0 -1056
- gobby/mcp_proxy/tools/task_expansion.py +0 -591
- gobby/prompts/defaults/expansion/system.md +0 -119
- gobby/prompts/defaults/expansion/user.md +0 -48
- gobby/prompts/defaults/external_validation/agent.md +0 -72
- gobby/prompts/defaults/external_validation/external.md +0 -63
- gobby/prompts/defaults/external_validation/spawn.md +0 -83
- gobby/prompts/defaults/external_validation/system.md +0 -6
- gobby/prompts/defaults/features/import_mcp.md +0 -22
- gobby/prompts/defaults/features/import_mcp_github.md +0 -17
- gobby/prompts/defaults/features/import_mcp_search.md +0 -16
- gobby/prompts/defaults/features/recommend_tools.md +0 -32
- gobby/prompts/defaults/features/recommend_tools_hybrid.md +0 -35
- gobby/prompts/defaults/features/recommend_tools_llm.md +0 -30
- gobby/prompts/defaults/features/server_description.md +0 -20
- gobby/prompts/defaults/features/server_description_system.md +0 -6
- gobby/prompts/defaults/features/task_description.md +0 -31
- gobby/prompts/defaults/features/task_description_system.md +0 -6
- gobby/prompts/defaults/features/tool_summary.md +0 -17
- gobby/prompts/defaults/features/tool_summary_system.md +0 -6
- gobby/prompts/defaults/research/step.md +0 -58
- gobby/prompts/defaults/validation/criteria.md +0 -47
- gobby/prompts/defaults/validation/validate.md +0 -38
- gobby/storage/migrations_legacy.py +0 -1359
- gobby/tasks/context.py +0 -747
- gobby/tasks/criteria.py +0 -342
- gobby/tasks/expansion.py +0 -626
- gobby/tasks/prompts/expand.py +0 -327
- gobby/tasks/research.py +0 -421
- gobby/tasks/tdd.py +0 -352
- {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/entry_points.txt +0 -0
- {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/licenses/LICENSE.md +0 -0
- {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/top_level.txt +0 -0
gobby/llm/codex_executor.py
CHANGED
|
@@ -1,30 +1,19 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Codex (OpenAI) implementation of AgentExecutor.
|
|
2
|
+
Codex (OpenAI) implementation of AgentExecutor for CLI/subscription mode only.
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
This executor spawns `codex exec --json` CLI and parses JSONL events.
|
|
5
|
+
It uses Codex's built-in tools (bash, file operations, etc.) - NO custom tool
|
|
6
|
+
injection is supported.
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
- Full tool injection support
|
|
9
|
-
- Requires OPENAI_API_KEY environment variable
|
|
10
|
-
|
|
11
|
-
2. subscription mode (ChatGPT Plus/Pro/Team/Enterprise):
|
|
12
|
-
- Spawns `codex exec --json` CLI and parses JSONL events
|
|
13
|
-
- Uses Codex's built-in tools (bash, file operations, etc.)
|
|
14
|
-
- NO custom tool injection - tools parameter is IGNORED
|
|
15
|
-
- Good for delegating complete autonomous tasks
|
|
16
|
-
|
|
17
|
-
IMPORTANT: These modes have fundamentally different capabilities.
|
|
18
|
-
Use api_key mode if you need custom MCP tool injection.
|
|
19
|
-
Use subscription mode for delegating complete tasks to Codex.
|
|
8
|
+
Note: api_key mode is now routed through LiteLLMExecutor for unified cost tracking.
|
|
9
|
+
Use the resolver.create_executor() function which handles routing automatically.
|
|
20
10
|
"""
|
|
21
11
|
|
|
22
12
|
import asyncio
|
|
23
13
|
import json
|
|
24
14
|
import logging
|
|
25
|
-
import os
|
|
26
15
|
import shutil
|
|
27
|
-
from typing import
|
|
16
|
+
from typing import Literal
|
|
28
17
|
|
|
29
18
|
from gobby.llm.executor import (
|
|
30
19
|
AgentExecutor,
|
|
@@ -36,39 +25,23 @@ from gobby.llm.executor import (
|
|
|
36
25
|
|
|
37
26
|
logger = logging.getLogger(__name__)
|
|
38
27
|
|
|
39
|
-
# Auth mode type
|
|
40
|
-
CodexAuthMode = Literal["
|
|
28
|
+
# Auth mode type - subscription/cli only, api_key routes through LiteLLM
|
|
29
|
+
CodexAuthMode = Literal["subscription", "cli"]
|
|
41
30
|
|
|
42
31
|
|
|
43
32
|
class CodexExecutor(AgentExecutor):
|
|
44
33
|
"""
|
|
45
|
-
Codex (OpenAI) implementation of AgentExecutor.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
subscription mode:
|
|
56
|
-
- Spawns `codex exec --json` CLI process
|
|
57
|
-
- Parses JSONL events (thread.started, item.completed, turn.completed)
|
|
58
|
-
- Uses Codex's built-in tools ONLY (bash, file ops, web search, etc.)
|
|
59
|
-
- The `tools` parameter is IGNORED in this mode
|
|
60
|
-
- Cannot inject custom MCP tools
|
|
61
|
-
- Best for delegating complete autonomous tasks
|
|
62
|
-
|
|
63
|
-
Example (api_key mode):
|
|
64
|
-
>>> executor = CodexExecutor(auth_mode="api_key")
|
|
65
|
-
>>> result = await executor.run(
|
|
66
|
-
... prompt="Create a task",
|
|
67
|
-
... tools=[ToolSchema(name="create_task", ...)],
|
|
68
|
-
... tool_handler=my_handler,
|
|
69
|
-
... )
|
|
34
|
+
Codex (OpenAI) implementation of AgentExecutor for CLI mode only.
|
|
35
|
+
|
|
36
|
+
Spawns `codex exec --json` CLI process and parses JSONL events.
|
|
37
|
+
Uses Codex's built-in tools ONLY (bash, file ops, web search, etc.).
|
|
38
|
+
The `tools` parameter is IGNORED - cannot inject custom MCP tools.
|
|
39
|
+
Best for delegating complete autonomous tasks.
|
|
40
|
+
|
|
41
|
+
For api_key mode with custom tool injection, use LiteLLMExecutor with
|
|
42
|
+
provider="codex" which routes through OpenAI API for unified cost tracking.
|
|
70
43
|
|
|
71
|
-
Example
|
|
44
|
+
Example:
|
|
72
45
|
>>> executor = CodexExecutor(auth_mode="subscription")
|
|
73
46
|
>>> result = await executor.run(
|
|
74
47
|
... prompt="Fix the bug in main.py and run the tests",
|
|
@@ -77,83 +50,49 @@ class CodexExecutor(AgentExecutor):
|
|
|
77
50
|
... )
|
|
78
51
|
"""
|
|
79
52
|
|
|
53
|
+
_cli_path: str
|
|
54
|
+
|
|
80
55
|
def __init__(
|
|
81
56
|
self,
|
|
82
|
-
auth_mode: CodexAuthMode = "
|
|
83
|
-
api_key: str | None = None,
|
|
57
|
+
auth_mode: CodexAuthMode = "subscription",
|
|
84
58
|
default_model: str = "gpt-4o",
|
|
85
59
|
):
|
|
86
60
|
"""
|
|
87
|
-
Initialize CodexExecutor.
|
|
61
|
+
Initialize CodexExecutor for CLI/subscription mode.
|
|
88
62
|
|
|
89
63
|
Args:
|
|
90
|
-
auth_mode:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
64
|
+
auth_mode: Must be "subscription" or "cli". API key mode is handled by LiteLLMExecutor.
|
|
65
|
+
default_model: Default model (not used in CLI mode, kept for interface compatibility).
|
|
66
|
+
|
|
67
|
+
Raises:
|
|
68
|
+
ValueError: If auth_mode is not "subscription"/"cli" or Codex CLI not found.
|
|
95
69
|
"""
|
|
70
|
+
if auth_mode not in ("subscription", "cli"):
|
|
71
|
+
raise ValueError(
|
|
72
|
+
"CodexExecutor only supports subscription/cli mode. "
|
|
73
|
+
"For api_key mode with custom tools, use LiteLLMExecutor with provider='codex'."
|
|
74
|
+
)
|
|
75
|
+
|
|
96
76
|
self.auth_mode = auth_mode
|
|
97
77
|
self.default_model = default_model
|
|
98
78
|
self.logger = logger
|
|
99
|
-
self.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
try:
|
|
111
|
-
from openai import AsyncOpenAI
|
|
112
|
-
|
|
113
|
-
self._client = AsyncOpenAI(api_key=key)
|
|
114
|
-
self.logger.debug("CodexExecutor initialized with API key")
|
|
115
|
-
except ImportError as e:
|
|
116
|
-
raise ImportError(
|
|
117
|
-
"openai package not found. Please install with `pip install openai`."
|
|
118
|
-
) from e
|
|
119
|
-
|
|
120
|
-
elif auth_mode == "subscription":
|
|
121
|
-
# Verify Codex CLI is available
|
|
122
|
-
cli_path = shutil.which("codex")
|
|
123
|
-
if not cli_path:
|
|
124
|
-
raise ValueError(
|
|
125
|
-
"Codex CLI not found in PATH. "
|
|
126
|
-
"Install Codex CLI and run `codex login` for subscription mode."
|
|
127
|
-
)
|
|
128
|
-
self._cli_path = cli_path
|
|
129
|
-
self.logger.debug(f"CodexExecutor initialized with CLI at {cli_path}")
|
|
130
|
-
|
|
131
|
-
else:
|
|
132
|
-
raise ValueError(f"Unknown auth_mode: {auth_mode}")
|
|
79
|
+
self._cli_path = ""
|
|
80
|
+
|
|
81
|
+
# Verify Codex CLI is available
|
|
82
|
+
cli_path = shutil.which("codex")
|
|
83
|
+
if not cli_path:
|
|
84
|
+
raise ValueError(
|
|
85
|
+
"Codex CLI not found in PATH. "
|
|
86
|
+
"Install Codex CLI and run `codex login` for subscription mode."
|
|
87
|
+
)
|
|
88
|
+
self._cli_path = cli_path
|
|
89
|
+
self.logger.debug(f"CodexExecutor initialized with CLI at {cli_path}")
|
|
133
90
|
|
|
134
91
|
@property
|
|
135
92
|
def provider_name(self) -> str:
|
|
136
93
|
"""Return the provider name."""
|
|
137
94
|
return "codex"
|
|
138
95
|
|
|
139
|
-
def _convert_tools_to_openai_format(self, tools: list[ToolSchema]) -> list[dict[str, Any]]:
|
|
140
|
-
"""Convert ToolSchema list to OpenAI function calling format."""
|
|
141
|
-
openai_tools = []
|
|
142
|
-
for tool in tools:
|
|
143
|
-
# Ensure input_schema has "type": "object"
|
|
144
|
-
params = {"type": "object", **tool.input_schema}
|
|
145
|
-
openai_tools.append(
|
|
146
|
-
{
|
|
147
|
-
"type": "function",
|
|
148
|
-
"function": {
|
|
149
|
-
"name": tool.name,
|
|
150
|
-
"description": tool.description,
|
|
151
|
-
"parameters": params,
|
|
152
|
-
},
|
|
153
|
-
}
|
|
154
|
-
)
|
|
155
|
-
return openai_tools
|
|
156
|
-
|
|
157
96
|
async def run(
|
|
158
97
|
self,
|
|
159
98
|
prompt: str,
|
|
@@ -165,200 +104,29 @@ class CodexExecutor(AgentExecutor):
|
|
|
165
104
|
timeout: float = 120.0,
|
|
166
105
|
) -> AgentResult:
|
|
167
106
|
"""
|
|
168
|
-
Execute
|
|
107
|
+
Execute Codex CLI and parse JSONL events.
|
|
108
|
+
|
|
109
|
+
Note: The tools and tool_handler parameters are IGNORED in CLI mode.
|
|
110
|
+
Codex uses its own built-in tools (bash, file operations, etc.).
|
|
169
111
|
|
|
170
|
-
For
|
|
171
|
-
For subscription mode: Spawns Codex CLI (tools parameter is IGNORED).
|
|
112
|
+
For custom tool injection, use LiteLLMExecutor with provider="codex".
|
|
172
113
|
|
|
173
114
|
Args:
|
|
174
115
|
prompt: The user prompt to process.
|
|
175
|
-
tools:
|
|
176
|
-
tool_handler:
|
|
177
|
-
system_prompt:
|
|
178
|
-
model:
|
|
179
|
-
max_turns:
|
|
116
|
+
tools: IGNORED - Codex uses its own tools.
|
|
117
|
+
tool_handler: IGNORED - not called in CLI mode.
|
|
118
|
+
system_prompt: IGNORED in CLI mode.
|
|
119
|
+
model: IGNORED in CLI mode.
|
|
120
|
+
max_turns: IGNORED in CLI mode.
|
|
180
121
|
timeout: Maximum execution time in seconds.
|
|
181
122
|
|
|
182
123
|
Returns:
|
|
183
124
|
AgentResult with output, status, and tool call records.
|
|
184
125
|
"""
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
tool_handler=tool_handler,
|
|
190
|
-
system_prompt=system_prompt,
|
|
191
|
-
model=model or self.default_model,
|
|
192
|
-
max_turns=max_turns,
|
|
193
|
-
timeout=timeout,
|
|
194
|
-
)
|
|
195
|
-
else:
|
|
196
|
-
return await self._run_with_cli(
|
|
197
|
-
prompt=prompt,
|
|
198
|
-
timeout=timeout,
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
async def _run_with_api(
|
|
202
|
-
self,
|
|
203
|
-
prompt: str,
|
|
204
|
-
tools: list[ToolSchema],
|
|
205
|
-
tool_handler: ToolHandler,
|
|
206
|
-
system_prompt: str | None,
|
|
207
|
-
model: str,
|
|
208
|
-
max_turns: int,
|
|
209
|
-
timeout: float,
|
|
210
|
-
) -> AgentResult:
|
|
211
|
-
"""Run using OpenAI API with function calling."""
|
|
212
|
-
if self._client is None:
|
|
213
|
-
return AgentResult(
|
|
214
|
-
output="",
|
|
215
|
-
status="error",
|
|
216
|
-
error="OpenAI client not initialized",
|
|
217
|
-
turns_used=0,
|
|
218
|
-
)
|
|
219
|
-
|
|
220
|
-
tool_calls_list: list[ToolCallRecord] = []
|
|
221
|
-
openai_tools = self._convert_tools_to_openai_format(tools)
|
|
222
|
-
|
|
223
|
-
# Build initial messages
|
|
224
|
-
messages: list[dict[str, Any]] = []
|
|
225
|
-
if system_prompt:
|
|
226
|
-
messages.append({"role": "system", "content": system_prompt})
|
|
227
|
-
messages.append({"role": "user", "content": prompt})
|
|
228
|
-
|
|
229
|
-
# Track turns in outer scope so timeout handler can access the count
|
|
230
|
-
turns_counter = [0]
|
|
231
|
-
|
|
232
|
-
async def _run_loop() -> AgentResult:
|
|
233
|
-
nonlocal messages
|
|
234
|
-
turns_used = 0
|
|
235
|
-
final_output = ""
|
|
236
|
-
client = self._client
|
|
237
|
-
if client is None:
|
|
238
|
-
raise RuntimeError("CodexExecutor client not initialized")
|
|
239
|
-
|
|
240
|
-
while turns_used < max_turns:
|
|
241
|
-
turns_used += 1
|
|
242
|
-
turns_counter[0] = turns_used
|
|
243
|
-
|
|
244
|
-
# Call OpenAI
|
|
245
|
-
try:
|
|
246
|
-
response = await client.chat.completions.create(
|
|
247
|
-
model=model,
|
|
248
|
-
messages=messages,
|
|
249
|
-
tools=openai_tools if openai_tools else None,
|
|
250
|
-
max_tokens=8192,
|
|
251
|
-
)
|
|
252
|
-
except Exception as e:
|
|
253
|
-
self.logger.error(f"OpenAI API error: {e}")
|
|
254
|
-
return AgentResult(
|
|
255
|
-
output="",
|
|
256
|
-
status="error",
|
|
257
|
-
tool_calls=tool_calls_list,
|
|
258
|
-
error=f"OpenAI API error: {e}",
|
|
259
|
-
turns_used=turns_used,
|
|
260
|
-
)
|
|
261
|
-
|
|
262
|
-
# Get the assistant's message
|
|
263
|
-
choice = response.choices[0]
|
|
264
|
-
message = choice.message
|
|
265
|
-
|
|
266
|
-
# Extract text content
|
|
267
|
-
if message.content:
|
|
268
|
-
final_output = message.content
|
|
269
|
-
|
|
270
|
-
# Add assistant message to history
|
|
271
|
-
messages.append(message.model_dump())
|
|
272
|
-
|
|
273
|
-
# Check if there are tool calls
|
|
274
|
-
if not message.tool_calls:
|
|
275
|
-
# No tool calls - we're done
|
|
276
|
-
return AgentResult(
|
|
277
|
-
output=final_output,
|
|
278
|
-
status="success",
|
|
279
|
-
tool_calls=tool_calls_list,
|
|
280
|
-
turns_used=turns_used,
|
|
281
|
-
)
|
|
282
|
-
|
|
283
|
-
# Handle tool calls
|
|
284
|
-
for tool_call in message.tool_calls:
|
|
285
|
-
tool_name = tool_call.function.name
|
|
286
|
-
try:
|
|
287
|
-
arguments = json.loads(tool_call.function.arguments)
|
|
288
|
-
except json.JSONDecodeError as e:
|
|
289
|
-
self.logger.warning(
|
|
290
|
-
f"Failed to parse tool call arguments for '{tool_name}' "
|
|
291
|
-
f"(id={getattr(tool_call, 'id', 'unknown')}): {e}. "
|
|
292
|
-
f"Arguments: {tool_call.function.arguments!r}"
|
|
293
|
-
)
|
|
294
|
-
arguments = {}
|
|
295
|
-
|
|
296
|
-
# Record the tool call
|
|
297
|
-
record = ToolCallRecord(
|
|
298
|
-
tool_name=tool_name,
|
|
299
|
-
arguments=arguments,
|
|
300
|
-
)
|
|
301
|
-
tool_calls_list.append(record)
|
|
302
|
-
|
|
303
|
-
# Execute via handler
|
|
304
|
-
try:
|
|
305
|
-
result = await tool_handler(tool_name, arguments)
|
|
306
|
-
record.result = result
|
|
307
|
-
|
|
308
|
-
# Format result for OpenAI
|
|
309
|
-
if result.success:
|
|
310
|
-
content = json.dumps(result.result) if result.result else "Success"
|
|
311
|
-
else:
|
|
312
|
-
content = f"Error: {result.error}"
|
|
313
|
-
|
|
314
|
-
except Exception as e:
|
|
315
|
-
self.logger.error(f"Tool handler error for {tool_name}: {e}")
|
|
316
|
-
from gobby.llm.executor import ToolResult as TR
|
|
317
|
-
|
|
318
|
-
record.result = TR(
|
|
319
|
-
tool_name=tool_name,
|
|
320
|
-
success=False,
|
|
321
|
-
error=str(e),
|
|
322
|
-
)
|
|
323
|
-
content = f"Error: {e}"
|
|
324
|
-
|
|
325
|
-
# Add tool result to messages
|
|
326
|
-
messages.append(
|
|
327
|
-
{
|
|
328
|
-
"role": "tool",
|
|
329
|
-
"tool_call_id": tool_call.id,
|
|
330
|
-
"content": content,
|
|
331
|
-
}
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
# Check finish reason
|
|
335
|
-
if choice.finish_reason == "stop":
|
|
336
|
-
return AgentResult(
|
|
337
|
-
output=final_output,
|
|
338
|
-
status="success",
|
|
339
|
-
tool_calls=tool_calls_list,
|
|
340
|
-
turns_used=turns_used,
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
# Max turns reached
|
|
344
|
-
return AgentResult(
|
|
345
|
-
output=final_output,
|
|
346
|
-
status="partial",
|
|
347
|
-
tool_calls=tool_calls_list,
|
|
348
|
-
turns_used=turns_used,
|
|
349
|
-
)
|
|
350
|
-
|
|
351
|
-
# Run with timeout
|
|
352
|
-
try:
|
|
353
|
-
return await asyncio.wait_for(_run_loop(), timeout=timeout)
|
|
354
|
-
except TimeoutError:
|
|
355
|
-
return AgentResult(
|
|
356
|
-
output="",
|
|
357
|
-
status="timeout",
|
|
358
|
-
tool_calls=tool_calls_list,
|
|
359
|
-
error=f"Execution timed out after {timeout}s",
|
|
360
|
-
turns_used=turns_counter[0],
|
|
361
|
-
)
|
|
126
|
+
return await self._run_with_cli(
|
|
127
|
+
prompt=prompt,
|
|
128
|
+
timeout=timeout,
|
|
129
|
+
)
|
|
362
130
|
|
|
363
131
|
async def _run_with_cli(
|
|
364
132
|
self,
|
gobby/llm/executor.py
CHANGED
|
@@ -59,6 +59,23 @@ class ToolCallRecord:
|
|
|
59
59
|
"""Result from the tool execution."""
|
|
60
60
|
|
|
61
61
|
|
|
62
|
+
@dataclass
|
|
63
|
+
class CostInfo:
|
|
64
|
+
"""Cost information from an LLM call."""
|
|
65
|
+
|
|
66
|
+
prompt_tokens: int = 0
|
|
67
|
+
"""Number of tokens in the prompt."""
|
|
68
|
+
|
|
69
|
+
completion_tokens: int = 0
|
|
70
|
+
"""Number of tokens in the completion."""
|
|
71
|
+
|
|
72
|
+
total_cost: float = 0.0
|
|
73
|
+
"""Total cost in USD for this call."""
|
|
74
|
+
|
|
75
|
+
model: str = ""
|
|
76
|
+
"""Model used for this call (LiteLLM format with prefix)."""
|
|
77
|
+
|
|
78
|
+
|
|
62
79
|
@dataclass
|
|
63
80
|
class AgentResult:
|
|
64
81
|
"""Result from running an agent to completion."""
|
|
@@ -93,6 +110,9 @@ class AgentResult:
|
|
|
93
110
|
child_session_id: str | None = None
|
|
94
111
|
"""ID of the child session created for this agent (set by AgentRunner)."""
|
|
95
112
|
|
|
113
|
+
cost_info: CostInfo | None = None
|
|
114
|
+
"""Cost tracking information (populated by LiteLLM executor)."""
|
|
115
|
+
|
|
96
116
|
|
|
97
117
|
# Type alias for the tool handler callback
|
|
98
118
|
ToolHandler = Callable[[str, dict[str, Any]], Awaitable[ToolResult]]
|
|
@@ -310,6 +330,7 @@ class AgentExecutor(ABC):
|
|
|
310
330
|
if completion_result is not None:
|
|
311
331
|
completion_result.tool_calls = result.tool_calls
|
|
312
332
|
completion_result.turns_used = result.turns_used
|
|
333
|
+
completion_result.cost_info = result.cost_info
|
|
313
334
|
return completion_result
|
|
314
335
|
|
|
315
336
|
# Otherwise, return the raw result (might be timeout or natural end)
|