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
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Discovery endpoints for MCP tool and server listing.
|
|
3
|
+
|
|
4
|
+
Extracted from tools.py as part of Phase 2 Strangler Fig decomposition.
|
|
5
|
+
These endpoints handle tool discovery, search, and recommendations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
import time
|
|
12
|
+
from typing import TYPE_CHECKING, Any
|
|
13
|
+
|
|
14
|
+
from fastapi import Depends, HTTPException, Request
|
|
15
|
+
|
|
16
|
+
from gobby.servers.routes.dependencies import get_metrics_manager, get_server
|
|
17
|
+
from gobby.utils.metrics import get_metrics_collector
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from gobby.mcp_proxy.metrics import ToolMetricsManager
|
|
21
|
+
from gobby.servers.http import HTTPServer
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
# Module-level metrics collector (shared across all requests)
|
|
26
|
+
_metrics = get_metrics_collector()
|
|
27
|
+
|
|
28
|
+
# Set to keep background tasks alive (prevent garbage collection)
|
|
29
|
+
_background_tasks: set[asyncio.Task[Any]] = set()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def list_all_mcp_tools(
|
|
33
|
+
server_filter: str | None = None,
|
|
34
|
+
include_metrics: bool = False,
|
|
35
|
+
project_id: str | None = None,
|
|
36
|
+
server: "HTTPServer" = Depends(get_server),
|
|
37
|
+
metrics_manager: "ToolMetricsManager | None" = Depends(get_metrics_manager),
|
|
38
|
+
) -> dict[str, Any]:
|
|
39
|
+
"""
|
|
40
|
+
List tools from MCP servers.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
server_filter: Optional server name to filter by
|
|
44
|
+
include_metrics: When True, include call_count, success_rate, avg_latency for each tool
|
|
45
|
+
project_id: Project ID for metrics lookup (uses current project if not specified)
|
|
46
|
+
server: HTTPServer instance (injected)
|
|
47
|
+
metrics_manager: Tool metrics manager (injected)
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Dict of server names to tool lists
|
|
51
|
+
"""
|
|
52
|
+
start_time = time.perf_counter()
|
|
53
|
+
_metrics.inc_counter("http_requests_total")
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
tools_by_server: dict[str, list[dict[str, Any]]] = {}
|
|
57
|
+
|
|
58
|
+
# Resolve project_id for metrics lookup
|
|
59
|
+
resolved_project_id = None
|
|
60
|
+
if include_metrics:
|
|
61
|
+
try:
|
|
62
|
+
resolved_project_id = server._resolve_project_id(project_id, cwd=None)
|
|
63
|
+
except ValueError:
|
|
64
|
+
# Project not initialized; skip metrics enrichment
|
|
65
|
+
resolved_project_id = None
|
|
66
|
+
|
|
67
|
+
# If specific server requested
|
|
68
|
+
if server_filter:
|
|
69
|
+
# Check internal first
|
|
70
|
+
if server._internal_manager and server._internal_manager.is_internal(server_filter):
|
|
71
|
+
registry = server._internal_manager.get_registry(server_filter)
|
|
72
|
+
if registry:
|
|
73
|
+
tools_by_server[server_filter] = registry.list_tools()
|
|
74
|
+
elif server.mcp_manager and server.mcp_manager.has_server(server_filter):
|
|
75
|
+
# Check if server is enabled before attempting connection
|
|
76
|
+
server_config = server.mcp_manager._configs.get(server_filter)
|
|
77
|
+
if server_config and not server_config.enabled:
|
|
78
|
+
tools_by_server[server_filter] = []
|
|
79
|
+
else:
|
|
80
|
+
try:
|
|
81
|
+
# Use ensure_connected for lazy loading
|
|
82
|
+
session = await server.mcp_manager.ensure_connected(server_filter)
|
|
83
|
+
tools_result = await session.list_tools()
|
|
84
|
+
tools_list = []
|
|
85
|
+
for t in tools_result.tools:
|
|
86
|
+
desc = getattr(t, "description", "") or ""
|
|
87
|
+
tools_list.append(
|
|
88
|
+
{
|
|
89
|
+
"name": t.name,
|
|
90
|
+
"brief": desc[:100],
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
tools_by_server[server_filter] = tools_list
|
|
94
|
+
except Exception as e:
|
|
95
|
+
logger.warning(f"Failed to list tools from {server_filter}: {e}")
|
|
96
|
+
tools_by_server[server_filter] = []
|
|
97
|
+
else:
|
|
98
|
+
# Get tools from all servers
|
|
99
|
+
# Internal servers
|
|
100
|
+
if server._internal_manager:
|
|
101
|
+
for registry in server._internal_manager.get_all_registries():
|
|
102
|
+
tools_by_server[registry.name] = registry.list_tools()
|
|
103
|
+
|
|
104
|
+
# External MCP servers - use ensure_connected for lazy loading
|
|
105
|
+
if server.mcp_manager:
|
|
106
|
+
for config in server.mcp_manager.server_configs:
|
|
107
|
+
if config.enabled:
|
|
108
|
+
try:
|
|
109
|
+
session = await server.mcp_manager.ensure_connected(config.name)
|
|
110
|
+
tools_result = await session.list_tools()
|
|
111
|
+
tools_list = []
|
|
112
|
+
for t in tools_result.tools:
|
|
113
|
+
desc = getattr(t, "description", "") or ""
|
|
114
|
+
tools_list.append(
|
|
115
|
+
{
|
|
116
|
+
"name": t.name,
|
|
117
|
+
"brief": desc[:100],
|
|
118
|
+
}
|
|
119
|
+
)
|
|
120
|
+
tools_by_server[config.name] = tools_list
|
|
121
|
+
except Exception as e:
|
|
122
|
+
logger.warning(f"Failed to list tools from {config.name}: {e}")
|
|
123
|
+
tools_by_server[config.name] = []
|
|
124
|
+
|
|
125
|
+
# Enrich with metrics if requested
|
|
126
|
+
if include_metrics and metrics_manager and resolved_project_id:
|
|
127
|
+
# Get all metrics for this project
|
|
128
|
+
metrics_data = metrics_manager.get_metrics(project_id=resolved_project_id)
|
|
129
|
+
metrics_by_key = {
|
|
130
|
+
(m["server_name"], m["tool_name"]): m for m in metrics_data.get("tools", [])
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for server_name, tools_list in tools_by_server.items():
|
|
134
|
+
for tool in tools_list:
|
|
135
|
+
# Guard against non-dict or missing-name entries
|
|
136
|
+
if not isinstance(tool, dict) or "name" not in tool:
|
|
137
|
+
continue
|
|
138
|
+
tool_name = tool.get("name")
|
|
139
|
+
key = (server_name, tool_name)
|
|
140
|
+
if key in metrics_by_key:
|
|
141
|
+
m = metrics_by_key[key]
|
|
142
|
+
tool["call_count"] = m.get("call_count", 0)
|
|
143
|
+
tool["success_rate"] = m.get("success_rate")
|
|
144
|
+
tool["avg_latency_ms"] = m.get("avg_latency_ms")
|
|
145
|
+
else:
|
|
146
|
+
tool["call_count"] = 0
|
|
147
|
+
tool["success_rate"] = None
|
|
148
|
+
tool["avg_latency_ms"] = None
|
|
149
|
+
|
|
150
|
+
response_time_ms = (time.perf_counter() - start_time) * 1000
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
"tools": tools_by_server,
|
|
154
|
+
"response_time_ms": response_time_ms,
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
except Exception as e:
|
|
158
|
+
_metrics.inc_counter("http_requests_errors_total")
|
|
159
|
+
logger.error(f"List MCP tools error: {e}", exc_info=True)
|
|
160
|
+
raise HTTPException(status_code=500, detail={"success": False, "error": str(e)}) from e
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
async def recommend_mcp_tools(
|
|
164
|
+
request: Request,
|
|
165
|
+
server: "HTTPServer" = Depends(get_server),
|
|
166
|
+
) -> dict[str, Any]:
|
|
167
|
+
"""
|
|
168
|
+
Get AI-powered tool recommendations for a task.
|
|
169
|
+
|
|
170
|
+
Request body:
|
|
171
|
+
{
|
|
172
|
+
"task_description": "I need to query a database",
|
|
173
|
+
"agent_id": "optional-agent-id",
|
|
174
|
+
"search_mode": "llm" | "semantic" | "hybrid",
|
|
175
|
+
"top_k": 10,
|
|
176
|
+
"min_similarity": 0.3,
|
|
177
|
+
"cwd": "/path/to/project"
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
List of tool recommendations
|
|
182
|
+
"""
|
|
183
|
+
start_time = time.perf_counter()
|
|
184
|
+
_metrics.inc_counter("http_requests_total")
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
try:
|
|
188
|
+
body = await request.json()
|
|
189
|
+
except json.JSONDecodeError as err:
|
|
190
|
+
raise HTTPException(
|
|
191
|
+
status_code=400,
|
|
192
|
+
detail={"success": False, "error": "Malformed JSON", "message": str(err)},
|
|
193
|
+
) from err
|
|
194
|
+
|
|
195
|
+
task_description = body.get("task_description")
|
|
196
|
+
agent_id = body.get("agent_id")
|
|
197
|
+
search_mode = body.get("search_mode", "llm")
|
|
198
|
+
top_k = body.get("top_k", 10)
|
|
199
|
+
min_similarity = body.get("min_similarity", 0.3)
|
|
200
|
+
cwd = body.get("cwd")
|
|
201
|
+
|
|
202
|
+
if not task_description:
|
|
203
|
+
raise HTTPException(
|
|
204
|
+
status_code=400,
|
|
205
|
+
detail={"success": False, "error": "Required field: task_description"},
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# For semantic/hybrid modes, resolve project_id from cwd
|
|
209
|
+
project_id = None
|
|
210
|
+
if search_mode in ("semantic", "hybrid"):
|
|
211
|
+
try:
|
|
212
|
+
project_id = server._resolve_project_id(None, cwd)
|
|
213
|
+
except ValueError as e:
|
|
214
|
+
response_time_ms = (time.perf_counter() - start_time) * 1000
|
|
215
|
+
raise HTTPException(
|
|
216
|
+
status_code=400,
|
|
217
|
+
detail={
|
|
218
|
+
"success": False,
|
|
219
|
+
"error": str(e),
|
|
220
|
+
"task": task_description,
|
|
221
|
+
"response_time_ms": response_time_ms,
|
|
222
|
+
},
|
|
223
|
+
) from e
|
|
224
|
+
|
|
225
|
+
# Use tools handler if available
|
|
226
|
+
if server._tools_handler:
|
|
227
|
+
result = await server._tools_handler.recommend_tools(
|
|
228
|
+
task_description=task_description,
|
|
229
|
+
agent_id=agent_id,
|
|
230
|
+
search_mode=search_mode,
|
|
231
|
+
top_k=top_k,
|
|
232
|
+
min_similarity=min_similarity,
|
|
233
|
+
project_id=project_id,
|
|
234
|
+
)
|
|
235
|
+
response_time_ms = (time.perf_counter() - start_time) * 1000
|
|
236
|
+
result["response_time_ms"] = response_time_ms
|
|
237
|
+
return result
|
|
238
|
+
|
|
239
|
+
# Fallback: no tools handler
|
|
240
|
+
return {
|
|
241
|
+
"success": False,
|
|
242
|
+
"error": "Tools handler not initialized",
|
|
243
|
+
"recommendations": [],
|
|
244
|
+
"response_time_ms": (time.perf_counter() - start_time) * 1000,
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
except HTTPException:
|
|
248
|
+
raise
|
|
249
|
+
except Exception as e:
|
|
250
|
+
_metrics.inc_counter("http_requests_errors_total")
|
|
251
|
+
logger.error(f"Recommend tools error: {e}", exc_info=True)
|
|
252
|
+
raise HTTPException(status_code=500, detail={"success": False, "error": str(e)}) from e
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
async def search_mcp_tools(
|
|
256
|
+
request: Request,
|
|
257
|
+
server: "HTTPServer" = Depends(get_server),
|
|
258
|
+
) -> dict[str, Any]:
|
|
259
|
+
"""
|
|
260
|
+
Search for tools using semantic similarity.
|
|
261
|
+
|
|
262
|
+
Request body:
|
|
263
|
+
{
|
|
264
|
+
"query": "create a file",
|
|
265
|
+
"top_k": 10,
|
|
266
|
+
"min_similarity": 0.0,
|
|
267
|
+
"server": "optional-server-filter",
|
|
268
|
+
"cwd": "/path/to/project"
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
List of matching tools with similarity scores
|
|
273
|
+
"""
|
|
274
|
+
start_time = time.perf_counter()
|
|
275
|
+
_metrics.inc_counter("http_requests_total")
|
|
276
|
+
|
|
277
|
+
try:
|
|
278
|
+
try:
|
|
279
|
+
body = await request.json()
|
|
280
|
+
except json.JSONDecodeError as err:
|
|
281
|
+
raise HTTPException(
|
|
282
|
+
status_code=400,
|
|
283
|
+
detail={"success": False, "error": "Malformed JSON", "message": str(err)},
|
|
284
|
+
) from err
|
|
285
|
+
|
|
286
|
+
query = body.get("query")
|
|
287
|
+
top_k = body.get("top_k", 10)
|
|
288
|
+
min_similarity = body.get("min_similarity", 0.0)
|
|
289
|
+
server_filter = body.get("server")
|
|
290
|
+
cwd = body.get("cwd")
|
|
291
|
+
|
|
292
|
+
if not query:
|
|
293
|
+
raise HTTPException(
|
|
294
|
+
status_code=400,
|
|
295
|
+
detail={"success": False, "error": "Required field: query"},
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
# Resolve project_id from cwd
|
|
299
|
+
try:
|
|
300
|
+
project_id = server._resolve_project_id(None, cwd)
|
|
301
|
+
except ValueError as e:
|
|
302
|
+
response_time_ms = (time.perf_counter() - start_time) * 1000
|
|
303
|
+
raise HTTPException(
|
|
304
|
+
status_code=400,
|
|
305
|
+
detail={
|
|
306
|
+
"success": False,
|
|
307
|
+
"error": str(e),
|
|
308
|
+
"query": query,
|
|
309
|
+
"response_time_ms": response_time_ms,
|
|
310
|
+
},
|
|
311
|
+
) from e
|
|
312
|
+
|
|
313
|
+
# Use semantic search directly if available
|
|
314
|
+
if server._tools_handler and server._tools_handler._semantic_search:
|
|
315
|
+
try:
|
|
316
|
+
import asyncio
|
|
317
|
+
|
|
318
|
+
semantic_search = server._tools_handler._semantic_search
|
|
319
|
+
|
|
320
|
+
# Check if embeddings exist - if not, trigger background generation
|
|
321
|
+
existing = semantic_search.get_embeddings_for_project(project_id)
|
|
322
|
+
if not existing and server._mcp_db_manager:
|
|
323
|
+
logger.info(
|
|
324
|
+
f"No embeddings for project {project_id}, triggering background generation..."
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
# Wrapper to log exceptions from background embedding generation
|
|
328
|
+
async def _embed_with_error_handling(proj_id: str) -> None:
|
|
329
|
+
try:
|
|
330
|
+
await semantic_search.embed_all_tools(
|
|
331
|
+
project_id=proj_id,
|
|
332
|
+
mcp_manager=server._mcp_db_manager,
|
|
333
|
+
force=False,
|
|
334
|
+
)
|
|
335
|
+
except Exception as e:
|
|
336
|
+
logger.error(
|
|
337
|
+
f"Background embedding generation failed for project {proj_id}: {e}",
|
|
338
|
+
exc_info=True,
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
# Trigger embedding generation as background task (non-blocking)
|
|
342
|
+
task = asyncio.create_task(_embed_with_error_handling(project_id))
|
|
343
|
+
_background_tasks.add(task)
|
|
344
|
+
task.add_done_callback(_background_tasks.discard)
|
|
345
|
+
# Return early indicating embeddings are being generated
|
|
346
|
+
response_time_ms = (time.perf_counter() - start_time) * 1000
|
|
347
|
+
return {
|
|
348
|
+
"success": True,
|
|
349
|
+
"embeddings_generating": True,
|
|
350
|
+
"query": query,
|
|
351
|
+
"results": [],
|
|
352
|
+
"total_results": 0,
|
|
353
|
+
"message": "Embeddings are being generated. Please retry in a few seconds.",
|
|
354
|
+
"response_time_ms": response_time_ms,
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
results = await semantic_search.search_tools(
|
|
358
|
+
query=query,
|
|
359
|
+
project_id=project_id,
|
|
360
|
+
top_k=top_k,
|
|
361
|
+
min_similarity=min_similarity,
|
|
362
|
+
server_filter=server_filter,
|
|
363
|
+
)
|
|
364
|
+
response_time_ms = (time.perf_counter() - start_time) * 1000
|
|
365
|
+
return {
|
|
366
|
+
"success": True,
|
|
367
|
+
"query": query,
|
|
368
|
+
"results": [r.to_dict() for r in results],
|
|
369
|
+
"total_results": len(results),
|
|
370
|
+
"response_time_ms": response_time_ms,
|
|
371
|
+
}
|
|
372
|
+
except Exception as e:
|
|
373
|
+
logger.error(f"Semantic search failed: {e}")
|
|
374
|
+
response_time_ms = (time.perf_counter() - start_time) * 1000
|
|
375
|
+
raise HTTPException(
|
|
376
|
+
status_code=500,
|
|
377
|
+
detail={
|
|
378
|
+
"success": False,
|
|
379
|
+
"error": str(e),
|
|
380
|
+
"query": query,
|
|
381
|
+
"response_time_ms": response_time_ms,
|
|
382
|
+
},
|
|
383
|
+
) from e
|
|
384
|
+
|
|
385
|
+
# Fallback: no semantic search
|
|
386
|
+
return {
|
|
387
|
+
"success": False,
|
|
388
|
+
"error": "Semantic search not configured",
|
|
389
|
+
"results": [],
|
|
390
|
+
"response_time_ms": (time.perf_counter() - start_time) * 1000,
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
except HTTPException:
|
|
394
|
+
raise
|
|
395
|
+
except Exception as e:
|
|
396
|
+
_metrics.inc_counter("http_requests_errors_total")
|
|
397
|
+
logger.error(f"Search tools error: {e}", exc_info=True)
|
|
398
|
+
raise HTTPException(status_code=500, detail={"success": False, "error": str(e)}) from e
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
__all__ = [
|
|
402
|
+
"list_all_mcp_tools",
|
|
403
|
+
"recommend_mcp_tools",
|
|
404
|
+
"search_mcp_tools",
|
|
405
|
+
]
|