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/gemini_executor.py
DELETED
|
@@ -1,339 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Gemini implementation of AgentExecutor.
|
|
3
|
-
|
|
4
|
-
Supports two authentication modes:
|
|
5
|
-
- api_key: Use GEMINI_API_KEY environment variable or provided key
|
|
6
|
-
- adc: Use Google Application Default Credentials (gcloud auth)
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import asyncio
|
|
10
|
-
import logging
|
|
11
|
-
import os
|
|
12
|
-
from typing import Any, Literal
|
|
13
|
-
|
|
14
|
-
from gobby.llm.executor import (
|
|
15
|
-
AgentExecutor,
|
|
16
|
-
AgentResult,
|
|
17
|
-
ToolCallRecord,
|
|
18
|
-
ToolHandler,
|
|
19
|
-
ToolResult,
|
|
20
|
-
ToolSchema,
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
logger = logging.getLogger(__name__)
|
|
24
|
-
|
|
25
|
-
# Auth mode type
|
|
26
|
-
GeminiAuthMode = Literal["api_key", "adc"]
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class GeminiExecutor(AgentExecutor):
|
|
30
|
-
"""
|
|
31
|
-
Gemini implementation of AgentExecutor.
|
|
32
|
-
|
|
33
|
-
Supports two authentication modes:
|
|
34
|
-
- api_key: Uses GEMINI_API_KEY environment variable or provided key
|
|
35
|
-
- adc: Uses Google Application Default Credentials (run `gcloud auth application-default login`)
|
|
36
|
-
|
|
37
|
-
The executor implements a proper agentic loop:
|
|
38
|
-
1. Send prompt to Gemini with function declarations
|
|
39
|
-
2. When Gemini requests a function call, call tool_handler
|
|
40
|
-
3. Send function result back to Gemini
|
|
41
|
-
4. Repeat until Gemini stops requesting functions or limits are reached
|
|
42
|
-
|
|
43
|
-
Example:
|
|
44
|
-
>>> executor = GeminiExecutor(auth_mode="api_key", api_key="...")
|
|
45
|
-
>>> result = await executor.run(
|
|
46
|
-
... prompt="Create a task",
|
|
47
|
-
... tools=[ToolSchema(name="create_task", ...)],
|
|
48
|
-
... tool_handler=my_handler,
|
|
49
|
-
... )
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
def __init__(
|
|
53
|
-
self,
|
|
54
|
-
auth_mode: GeminiAuthMode = "api_key",
|
|
55
|
-
api_key: str | None = None,
|
|
56
|
-
default_model: str = "gemini-2.0-flash",
|
|
57
|
-
):
|
|
58
|
-
"""
|
|
59
|
-
Initialize GeminiExecutor.
|
|
60
|
-
|
|
61
|
-
Args:
|
|
62
|
-
auth_mode: Authentication mode ("api_key" or "adc").
|
|
63
|
-
api_key: Gemini API key (optional for api_key mode, uses GEMINI_API_KEY env var).
|
|
64
|
-
default_model: Default model to use if not specified in run().
|
|
65
|
-
"""
|
|
66
|
-
self.auth_mode = auth_mode
|
|
67
|
-
self.default_model = default_model
|
|
68
|
-
self.logger = logger
|
|
69
|
-
self._genai: Any = None
|
|
70
|
-
|
|
71
|
-
try:
|
|
72
|
-
import google.generativeai as genai
|
|
73
|
-
|
|
74
|
-
if auth_mode == "adc":
|
|
75
|
-
# Use Application Default Credentials
|
|
76
|
-
try:
|
|
77
|
-
import google.auth
|
|
78
|
-
|
|
79
|
-
credentials, _project = google.auth.default()
|
|
80
|
-
genai.configure(credentials=credentials)
|
|
81
|
-
self._genai = genai
|
|
82
|
-
self.logger.debug("Gemini initialized with ADC credentials")
|
|
83
|
-
except Exception as e:
|
|
84
|
-
raise ValueError(
|
|
85
|
-
f"Failed to initialize Gemini with ADC: {e}. "
|
|
86
|
-
"Run 'gcloud auth application-default login' to authenticate."
|
|
87
|
-
) from e
|
|
88
|
-
else:
|
|
89
|
-
# Use API key from parameter or environment
|
|
90
|
-
key = api_key or os.environ.get("GEMINI_API_KEY")
|
|
91
|
-
if not key:
|
|
92
|
-
raise ValueError(
|
|
93
|
-
"API key required for api_key mode. "
|
|
94
|
-
"Provide api_key parameter or set GEMINI_API_KEY env var."
|
|
95
|
-
)
|
|
96
|
-
genai.configure(api_key=key)
|
|
97
|
-
self._genai = genai
|
|
98
|
-
self.logger.debug("Gemini initialized with API key")
|
|
99
|
-
|
|
100
|
-
except ImportError as e:
|
|
101
|
-
raise ImportError(
|
|
102
|
-
"google-generativeai package not found. "
|
|
103
|
-
"Please install with `pip install google-generativeai`."
|
|
104
|
-
) from e
|
|
105
|
-
|
|
106
|
-
@property
|
|
107
|
-
def provider_name(self) -> str:
|
|
108
|
-
"""Return the provider name."""
|
|
109
|
-
return "gemini"
|
|
110
|
-
|
|
111
|
-
def _convert_tools_to_gemini_format(self, tools: list[ToolSchema]) -> list[dict[str, Any]]:
|
|
112
|
-
"""Convert ToolSchema list to Gemini function declarations format."""
|
|
113
|
-
function_declarations = []
|
|
114
|
-
for tool in tools:
|
|
115
|
-
# Build parameter schema
|
|
116
|
-
params = tool.input_schema.copy()
|
|
117
|
-
# Ensure type is object
|
|
118
|
-
if "type" not in params:
|
|
119
|
-
params["type"] = "object"
|
|
120
|
-
|
|
121
|
-
function_declarations.append(
|
|
122
|
-
{
|
|
123
|
-
"name": tool.name,
|
|
124
|
-
"description": tool.description,
|
|
125
|
-
"parameters": params,
|
|
126
|
-
}
|
|
127
|
-
)
|
|
128
|
-
return function_declarations
|
|
129
|
-
|
|
130
|
-
async def run(
|
|
131
|
-
self,
|
|
132
|
-
prompt: str,
|
|
133
|
-
tools: list[ToolSchema],
|
|
134
|
-
tool_handler: ToolHandler,
|
|
135
|
-
system_prompt: str | None = None,
|
|
136
|
-
model: str | None = None,
|
|
137
|
-
max_turns: int = 10,
|
|
138
|
-
timeout: float = 120.0,
|
|
139
|
-
) -> AgentResult:
|
|
140
|
-
"""
|
|
141
|
-
Execute an agentic loop with function calling.
|
|
142
|
-
|
|
143
|
-
Runs Gemini with the given prompt, calling tools via tool_handler
|
|
144
|
-
until completion, max_turns, or timeout.
|
|
145
|
-
|
|
146
|
-
Args:
|
|
147
|
-
prompt: The user prompt to process.
|
|
148
|
-
tools: List of available tools with their schemas.
|
|
149
|
-
tool_handler: Callback to execute tool calls.
|
|
150
|
-
system_prompt: Optional system prompt.
|
|
151
|
-
model: Optional model override.
|
|
152
|
-
max_turns: Maximum turns before stopping (default: 10).
|
|
153
|
-
timeout: Maximum execution time in seconds (default: 120.0).
|
|
154
|
-
|
|
155
|
-
Returns:
|
|
156
|
-
AgentResult with output, status, and tool call records.
|
|
157
|
-
"""
|
|
158
|
-
if self._genai is None:
|
|
159
|
-
return AgentResult(
|
|
160
|
-
output="",
|
|
161
|
-
status="error",
|
|
162
|
-
error="Gemini client not initialized",
|
|
163
|
-
turns_used=0,
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
tool_calls: list[ToolCallRecord] = []
|
|
167
|
-
effective_model = model or self.default_model
|
|
168
|
-
|
|
169
|
-
# Track turns in outer scope so timeout handler can access the count
|
|
170
|
-
turns_counter = [0]
|
|
171
|
-
|
|
172
|
-
async def _run_loop() -> AgentResult:
|
|
173
|
-
turns_used = 0
|
|
174
|
-
final_output = ""
|
|
175
|
-
genai = self._genai
|
|
176
|
-
if genai is None:
|
|
177
|
-
raise RuntimeError("GeminiExecutor genai not initialized")
|
|
178
|
-
|
|
179
|
-
# Create the model with tools
|
|
180
|
-
gemini_tools = self._convert_tools_to_gemini_format(tools)
|
|
181
|
-
|
|
182
|
-
# Create Tool instance (SDK expects Tool objects, not plain dicts)
|
|
183
|
-
tool_instance = None
|
|
184
|
-
if gemini_tools:
|
|
185
|
-
tool_instance = genai.protos.Tool(function_declarations=gemini_tools)
|
|
186
|
-
|
|
187
|
-
# Create model with system instruction
|
|
188
|
-
generation_config = {
|
|
189
|
-
"max_output_tokens": 8192,
|
|
190
|
-
"temperature": 0.7,
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
model_instance = genai.GenerativeModel(
|
|
194
|
-
model_name=effective_model,
|
|
195
|
-
system_instruction=system_prompt or "You are a helpful assistant.",
|
|
196
|
-
generation_config=generation_config,
|
|
197
|
-
tools=[tool_instance] if tool_instance else None,
|
|
198
|
-
)
|
|
199
|
-
|
|
200
|
-
# Start chat
|
|
201
|
-
chat = model_instance.start_chat()
|
|
202
|
-
|
|
203
|
-
# Send initial message
|
|
204
|
-
try:
|
|
205
|
-
response = await chat.send_message_async(prompt)
|
|
206
|
-
except Exception as e:
|
|
207
|
-
self.logger.error(f"Gemini API error: {e}")
|
|
208
|
-
return AgentResult(
|
|
209
|
-
output="",
|
|
210
|
-
status="error",
|
|
211
|
-
tool_calls=tool_calls,
|
|
212
|
-
error=f"Gemini API error: {e}",
|
|
213
|
-
turns_used=0,
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
while turns_used < max_turns:
|
|
217
|
-
turns_used += 1
|
|
218
|
-
turns_counter[0] = turns_used
|
|
219
|
-
|
|
220
|
-
# Extract function calls and text from response
|
|
221
|
-
function_calls: list[dict[str, Any]] = []
|
|
222
|
-
|
|
223
|
-
for candidate in response.candidates:
|
|
224
|
-
for part in candidate.content.parts:
|
|
225
|
-
# Check for text content
|
|
226
|
-
if hasattr(part, "text") and part.text:
|
|
227
|
-
final_output = part.text
|
|
228
|
-
|
|
229
|
-
# Check for function call
|
|
230
|
-
if hasattr(part, "function_call") and part.function_call:
|
|
231
|
-
fc = part.function_call
|
|
232
|
-
function_calls.append(
|
|
233
|
-
{
|
|
234
|
-
"name": fc.name,
|
|
235
|
-
"args": dict(fc.args) if fc.args else {},
|
|
236
|
-
}
|
|
237
|
-
)
|
|
238
|
-
|
|
239
|
-
# If no function calls, we're done
|
|
240
|
-
if not function_calls:
|
|
241
|
-
return AgentResult(
|
|
242
|
-
output=final_output,
|
|
243
|
-
status="success",
|
|
244
|
-
tool_calls=tool_calls,
|
|
245
|
-
turns_used=turns_used,
|
|
246
|
-
)
|
|
247
|
-
|
|
248
|
-
# Handle function calls
|
|
249
|
-
function_responses = []
|
|
250
|
-
|
|
251
|
-
for fc in function_calls:
|
|
252
|
-
tool_name = fc["name"]
|
|
253
|
-
arguments = fc["args"]
|
|
254
|
-
|
|
255
|
-
# Record the tool call
|
|
256
|
-
record = ToolCallRecord(
|
|
257
|
-
tool_name=tool_name,
|
|
258
|
-
arguments=arguments,
|
|
259
|
-
)
|
|
260
|
-
tool_calls.append(record)
|
|
261
|
-
|
|
262
|
-
# Execute via handler
|
|
263
|
-
try:
|
|
264
|
-
result = await tool_handler(tool_name, arguments)
|
|
265
|
-
record.result = result
|
|
266
|
-
|
|
267
|
-
# Format result for Gemini
|
|
268
|
-
if result.success:
|
|
269
|
-
# Use 'is not None' to preserve legitimate falsy values like 0, False, {}
|
|
270
|
-
response_data = (
|
|
271
|
-
result.result
|
|
272
|
-
if result.result is not None
|
|
273
|
-
else {"status": "success"}
|
|
274
|
-
)
|
|
275
|
-
else:
|
|
276
|
-
response_data = {"error": result.error}
|
|
277
|
-
|
|
278
|
-
function_responses.append(
|
|
279
|
-
genai.protos.Part(
|
|
280
|
-
function_response=genai.protos.FunctionResponse(
|
|
281
|
-
name=tool_name,
|
|
282
|
-
response=(
|
|
283
|
-
response_data
|
|
284
|
-
if isinstance(response_data, dict)
|
|
285
|
-
else {"result": response_data}
|
|
286
|
-
),
|
|
287
|
-
)
|
|
288
|
-
)
|
|
289
|
-
)
|
|
290
|
-
except Exception as e:
|
|
291
|
-
self.logger.error(f"Tool handler error for {tool_name}: {e}")
|
|
292
|
-
record.result = ToolResult(
|
|
293
|
-
tool_name=tool_name,
|
|
294
|
-
success=False,
|
|
295
|
-
error=str(e),
|
|
296
|
-
)
|
|
297
|
-
function_responses.append(
|
|
298
|
-
genai.protos.Part(
|
|
299
|
-
function_response=genai.protos.FunctionResponse(
|
|
300
|
-
name=tool_name,
|
|
301
|
-
response={"error": str(e)},
|
|
302
|
-
)
|
|
303
|
-
)
|
|
304
|
-
)
|
|
305
|
-
|
|
306
|
-
# Send function responses back to Gemini
|
|
307
|
-
try:
|
|
308
|
-
response = await chat.send_message_async(function_responses)
|
|
309
|
-
# Response will be processed in the next iteration of the while loop
|
|
310
|
-
# which extracts function calls and text directly from the response object
|
|
311
|
-
except Exception as e:
|
|
312
|
-
self.logger.error(f"Error sending function response: {e}")
|
|
313
|
-
return AgentResult(
|
|
314
|
-
output=final_output,
|
|
315
|
-
status="error",
|
|
316
|
-
tool_calls=tool_calls,
|
|
317
|
-
error=f"Error sending function response: {e}",
|
|
318
|
-
turns_used=turns_used,
|
|
319
|
-
)
|
|
320
|
-
|
|
321
|
-
# Max turns reached
|
|
322
|
-
return AgentResult(
|
|
323
|
-
output=final_output,
|
|
324
|
-
status="partial",
|
|
325
|
-
tool_calls=tool_calls,
|
|
326
|
-
turns_used=turns_used,
|
|
327
|
-
)
|
|
328
|
-
|
|
329
|
-
# Run with timeout
|
|
330
|
-
try:
|
|
331
|
-
return await asyncio.wait_for(_run_loop(), timeout=timeout)
|
|
332
|
-
except TimeoutError:
|
|
333
|
-
return AgentResult(
|
|
334
|
-
output="",
|
|
335
|
-
status="timeout",
|
|
336
|
-
tool_calls=tool_calls,
|
|
337
|
-
error=f"Execution timed out after {timeout}s",
|
|
338
|
-
turns_used=turns_counter[0],
|
|
339
|
-
)
|