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.
Files changed (244) hide show
  1. gobby/__init__.py +1 -1
  2. gobby/adapters/__init__.py +2 -1
  3. gobby/adapters/claude_code.py +13 -4
  4. gobby/adapters/codex_impl/__init__.py +28 -0
  5. gobby/adapters/codex_impl/adapter.py +722 -0
  6. gobby/adapters/codex_impl/client.py +679 -0
  7. gobby/adapters/codex_impl/protocol.py +20 -0
  8. gobby/adapters/codex_impl/types.py +68 -0
  9. gobby/agents/definitions.py +11 -1
  10. gobby/agents/isolation.py +395 -0
  11. gobby/agents/runner.py +8 -0
  12. gobby/agents/sandbox.py +261 -0
  13. gobby/agents/spawn.py +42 -287
  14. gobby/agents/spawn_executor.py +385 -0
  15. gobby/agents/spawners/__init__.py +24 -0
  16. gobby/agents/spawners/command_builder.py +189 -0
  17. gobby/agents/spawners/embedded.py +21 -2
  18. gobby/agents/spawners/headless.py +21 -2
  19. gobby/agents/spawners/prompt_manager.py +125 -0
  20. gobby/cli/__init__.py +6 -0
  21. gobby/cli/clones.py +419 -0
  22. gobby/cli/conductor.py +266 -0
  23. gobby/cli/install.py +4 -4
  24. gobby/cli/installers/antigravity.py +3 -9
  25. gobby/cli/installers/claude.py +15 -9
  26. gobby/cli/installers/codex.py +2 -8
  27. gobby/cli/installers/gemini.py +8 -8
  28. gobby/cli/installers/shared.py +175 -13
  29. gobby/cli/sessions.py +1 -1
  30. gobby/cli/skills.py +858 -0
  31. gobby/cli/tasks/ai.py +0 -440
  32. gobby/cli/tasks/crud.py +44 -6
  33. gobby/cli/tasks/main.py +0 -4
  34. gobby/cli/tui.py +2 -2
  35. gobby/cli/utils.py +12 -5
  36. gobby/clones/__init__.py +13 -0
  37. gobby/clones/git.py +547 -0
  38. gobby/conductor/__init__.py +16 -0
  39. gobby/conductor/alerts.py +135 -0
  40. gobby/conductor/loop.py +164 -0
  41. gobby/conductor/monitors/__init__.py +11 -0
  42. gobby/conductor/monitors/agents.py +116 -0
  43. gobby/conductor/monitors/tasks.py +155 -0
  44. gobby/conductor/pricing.py +234 -0
  45. gobby/conductor/token_tracker.py +160 -0
  46. gobby/config/__init__.py +12 -97
  47. gobby/config/app.py +69 -91
  48. gobby/config/extensions.py +2 -2
  49. gobby/config/features.py +7 -130
  50. gobby/config/search.py +110 -0
  51. gobby/config/servers.py +1 -1
  52. gobby/config/skills.py +43 -0
  53. gobby/config/tasks.py +9 -41
  54. gobby/hooks/__init__.py +0 -13
  55. gobby/hooks/event_handlers.py +188 -2
  56. gobby/hooks/hook_manager.py +50 -4
  57. gobby/hooks/plugins.py +1 -1
  58. gobby/hooks/skill_manager.py +130 -0
  59. gobby/hooks/webhooks.py +1 -1
  60. gobby/install/claude/hooks/hook_dispatcher.py +4 -4
  61. gobby/install/codex/hooks/hook_dispatcher.py +1 -1
  62. gobby/install/gemini/hooks/hook_dispatcher.py +87 -12
  63. gobby/llm/claude.py +22 -34
  64. gobby/llm/claude_executor.py +46 -256
  65. gobby/llm/codex_executor.py +59 -291
  66. gobby/llm/executor.py +21 -0
  67. gobby/llm/gemini.py +134 -110
  68. gobby/llm/litellm_executor.py +143 -6
  69. gobby/llm/resolver.py +98 -35
  70. gobby/mcp_proxy/importer.py +62 -4
  71. gobby/mcp_proxy/instructions.py +56 -0
  72. gobby/mcp_proxy/models.py +15 -0
  73. gobby/mcp_proxy/registries.py +68 -8
  74. gobby/mcp_proxy/server.py +33 -3
  75. gobby/mcp_proxy/services/recommendation.py +43 -11
  76. gobby/mcp_proxy/services/tool_proxy.py +81 -1
  77. gobby/mcp_proxy/stdio.py +2 -1
  78. gobby/mcp_proxy/tools/__init__.py +0 -2
  79. gobby/mcp_proxy/tools/agent_messaging.py +317 -0
  80. gobby/mcp_proxy/tools/agents.py +31 -731
  81. gobby/mcp_proxy/tools/clones.py +518 -0
  82. gobby/mcp_proxy/tools/memory.py +3 -26
  83. gobby/mcp_proxy/tools/metrics.py +65 -1
  84. gobby/mcp_proxy/tools/orchestration/__init__.py +3 -0
  85. gobby/mcp_proxy/tools/orchestration/cleanup.py +151 -0
  86. gobby/mcp_proxy/tools/orchestration/wait.py +467 -0
  87. gobby/mcp_proxy/tools/sessions/__init__.py +14 -0
  88. gobby/mcp_proxy/tools/sessions/_commits.py +232 -0
  89. gobby/mcp_proxy/tools/sessions/_crud.py +253 -0
  90. gobby/mcp_proxy/tools/sessions/_factory.py +63 -0
  91. gobby/mcp_proxy/tools/sessions/_handoff.py +499 -0
  92. gobby/mcp_proxy/tools/sessions/_messages.py +138 -0
  93. gobby/mcp_proxy/tools/skills/__init__.py +616 -0
  94. gobby/mcp_proxy/tools/spawn_agent.py +417 -0
  95. gobby/mcp_proxy/tools/task_orchestration.py +7 -0
  96. gobby/mcp_proxy/tools/task_readiness.py +14 -0
  97. gobby/mcp_proxy/tools/task_sync.py +1 -1
  98. gobby/mcp_proxy/tools/tasks/_context.py +0 -20
  99. gobby/mcp_proxy/tools/tasks/_crud.py +91 -4
  100. gobby/mcp_proxy/tools/tasks/_expansion.py +348 -0
  101. gobby/mcp_proxy/tools/tasks/_factory.py +6 -16
  102. gobby/mcp_proxy/tools/tasks/_lifecycle.py +110 -45
  103. gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +18 -29
  104. gobby/mcp_proxy/tools/workflows.py +1 -1
  105. gobby/mcp_proxy/tools/worktrees.py +0 -338
  106. gobby/memory/backends/__init__.py +6 -1
  107. gobby/memory/backends/mem0.py +6 -1
  108. gobby/memory/extractor.py +477 -0
  109. gobby/memory/ingestion/__init__.py +5 -0
  110. gobby/memory/ingestion/multimodal.py +221 -0
  111. gobby/memory/manager.py +73 -285
  112. gobby/memory/search/__init__.py +10 -0
  113. gobby/memory/search/coordinator.py +248 -0
  114. gobby/memory/services/__init__.py +5 -0
  115. gobby/memory/services/crossref.py +142 -0
  116. gobby/prompts/loader.py +5 -2
  117. gobby/runner.py +37 -16
  118. gobby/search/__init__.py +48 -6
  119. gobby/search/backends/__init__.py +159 -0
  120. gobby/search/backends/embedding.py +225 -0
  121. gobby/search/embeddings.py +238 -0
  122. gobby/search/models.py +148 -0
  123. gobby/search/unified.py +496 -0
  124. gobby/servers/http.py +24 -12
  125. gobby/servers/routes/admin.py +294 -0
  126. gobby/servers/routes/mcp/endpoints/__init__.py +61 -0
  127. gobby/servers/routes/mcp/endpoints/discovery.py +405 -0
  128. gobby/servers/routes/mcp/endpoints/execution.py +568 -0
  129. gobby/servers/routes/mcp/endpoints/registry.py +378 -0
  130. gobby/servers/routes/mcp/endpoints/server.py +304 -0
  131. gobby/servers/routes/mcp/hooks.py +1 -1
  132. gobby/servers/routes/mcp/tools.py +48 -1317
  133. gobby/servers/websocket.py +2 -2
  134. gobby/sessions/analyzer.py +2 -0
  135. gobby/sessions/lifecycle.py +1 -1
  136. gobby/sessions/processor.py +10 -0
  137. gobby/sessions/transcripts/base.py +2 -0
  138. gobby/sessions/transcripts/claude.py +79 -10
  139. gobby/skills/__init__.py +91 -0
  140. gobby/skills/loader.py +685 -0
  141. gobby/skills/manager.py +384 -0
  142. gobby/skills/parser.py +286 -0
  143. gobby/skills/search.py +463 -0
  144. gobby/skills/sync.py +119 -0
  145. gobby/skills/updater.py +385 -0
  146. gobby/skills/validator.py +368 -0
  147. gobby/storage/clones.py +378 -0
  148. gobby/storage/database.py +1 -1
  149. gobby/storage/memories.py +43 -13
  150. gobby/storage/migrations.py +162 -201
  151. gobby/storage/sessions.py +116 -7
  152. gobby/storage/skills.py +782 -0
  153. gobby/storage/tasks/_crud.py +4 -4
  154. gobby/storage/tasks/_lifecycle.py +57 -7
  155. gobby/storage/tasks/_manager.py +14 -5
  156. gobby/storage/tasks/_models.py +8 -3
  157. gobby/sync/memories.py +40 -5
  158. gobby/sync/tasks.py +83 -6
  159. gobby/tasks/__init__.py +1 -2
  160. gobby/tasks/external_validator.py +1 -1
  161. gobby/tasks/validation.py +46 -35
  162. gobby/tools/summarizer.py +91 -10
  163. gobby/tui/api_client.py +4 -7
  164. gobby/tui/app.py +5 -3
  165. gobby/tui/screens/orchestrator.py +1 -2
  166. gobby/tui/screens/tasks.py +2 -4
  167. gobby/tui/ws_client.py +1 -1
  168. gobby/utils/daemon_client.py +2 -2
  169. gobby/utils/project_context.py +2 -3
  170. gobby/utils/status.py +13 -0
  171. gobby/workflows/actions.py +221 -1135
  172. gobby/workflows/artifact_actions.py +31 -0
  173. gobby/workflows/autonomous_actions.py +11 -0
  174. gobby/workflows/context_actions.py +93 -1
  175. gobby/workflows/detection_helpers.py +115 -31
  176. gobby/workflows/enforcement/__init__.py +47 -0
  177. gobby/workflows/enforcement/blocking.py +269 -0
  178. gobby/workflows/enforcement/commit_policy.py +283 -0
  179. gobby/workflows/enforcement/handlers.py +269 -0
  180. gobby/workflows/{task_enforcement_actions.py → enforcement/task_policy.py} +29 -388
  181. gobby/workflows/engine.py +13 -2
  182. gobby/workflows/git_utils.py +106 -0
  183. gobby/workflows/lifecycle_evaluator.py +29 -1
  184. gobby/workflows/llm_actions.py +30 -0
  185. gobby/workflows/loader.py +19 -6
  186. gobby/workflows/mcp_actions.py +20 -1
  187. gobby/workflows/memory_actions.py +154 -0
  188. gobby/workflows/safe_evaluator.py +183 -0
  189. gobby/workflows/session_actions.py +44 -0
  190. gobby/workflows/state_actions.py +60 -1
  191. gobby/workflows/stop_signal_actions.py +55 -0
  192. gobby/workflows/summary_actions.py +111 -1
  193. gobby/workflows/task_sync_actions.py +347 -0
  194. gobby/workflows/todo_actions.py +34 -1
  195. gobby/workflows/webhook_actions.py +185 -0
  196. {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/METADATA +87 -21
  197. {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/RECORD +201 -172
  198. {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/WHEEL +1 -1
  199. gobby/adapters/codex.py +0 -1292
  200. gobby/install/claude/commands/gobby/bug.md +0 -51
  201. gobby/install/claude/commands/gobby/chore.md +0 -51
  202. gobby/install/claude/commands/gobby/epic.md +0 -52
  203. gobby/install/claude/commands/gobby/eval.md +0 -235
  204. gobby/install/claude/commands/gobby/feat.md +0 -49
  205. gobby/install/claude/commands/gobby/nit.md +0 -52
  206. gobby/install/claude/commands/gobby/ref.md +0 -52
  207. gobby/install/codex/prompts/forget.md +0 -7
  208. gobby/install/codex/prompts/memories.md +0 -7
  209. gobby/install/codex/prompts/recall.md +0 -7
  210. gobby/install/codex/prompts/remember.md +0 -13
  211. gobby/llm/gemini_executor.py +0 -339
  212. gobby/mcp_proxy/tools/session_messages.py +0 -1056
  213. gobby/mcp_proxy/tools/task_expansion.py +0 -591
  214. gobby/prompts/defaults/expansion/system.md +0 -119
  215. gobby/prompts/defaults/expansion/user.md +0 -48
  216. gobby/prompts/defaults/external_validation/agent.md +0 -72
  217. gobby/prompts/defaults/external_validation/external.md +0 -63
  218. gobby/prompts/defaults/external_validation/spawn.md +0 -83
  219. gobby/prompts/defaults/external_validation/system.md +0 -6
  220. gobby/prompts/defaults/features/import_mcp.md +0 -22
  221. gobby/prompts/defaults/features/import_mcp_github.md +0 -17
  222. gobby/prompts/defaults/features/import_mcp_search.md +0 -16
  223. gobby/prompts/defaults/features/recommend_tools.md +0 -32
  224. gobby/prompts/defaults/features/recommend_tools_hybrid.md +0 -35
  225. gobby/prompts/defaults/features/recommend_tools_llm.md +0 -30
  226. gobby/prompts/defaults/features/server_description.md +0 -20
  227. gobby/prompts/defaults/features/server_description_system.md +0 -6
  228. gobby/prompts/defaults/features/task_description.md +0 -31
  229. gobby/prompts/defaults/features/task_description_system.md +0 -6
  230. gobby/prompts/defaults/features/tool_summary.md +0 -17
  231. gobby/prompts/defaults/features/tool_summary_system.md +0 -6
  232. gobby/prompts/defaults/research/step.md +0 -58
  233. gobby/prompts/defaults/validation/criteria.md +0 -47
  234. gobby/prompts/defaults/validation/validate.md +0 -38
  235. gobby/storage/migrations_legacy.py +0 -1359
  236. gobby/tasks/context.py +0 -747
  237. gobby/tasks/criteria.py +0 -342
  238. gobby/tasks/expansion.py +0 -626
  239. gobby/tasks/prompts/expand.py +0 -327
  240. gobby/tasks/research.py +0 -421
  241. gobby/tasks/tdd.py +0 -352
  242. {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/entry_points.txt +0 -0
  243. {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/licenses/LICENSE.md +0 -0
  244. {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/top_level.txt +0 -0
@@ -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
- )