superqode 0.1.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- superqode/__init__.py +33 -0
- superqode/acp/__init__.py +23 -0
- superqode/acp/client.py +913 -0
- superqode/acp/permission_screen.py +457 -0
- superqode/acp/types.py +480 -0
- superqode/acp_discovery.py +856 -0
- superqode/agent/__init__.py +22 -0
- superqode/agent/edit_strategies.py +334 -0
- superqode/agent/loop.py +892 -0
- superqode/agent/qe_report_templates.py +39 -0
- superqode/agent/system_prompts.py +353 -0
- superqode/agent_output.py +721 -0
- superqode/agent_stream.py +953 -0
- superqode/agents/__init__.py +59 -0
- superqode/agents/acp_registry.py +305 -0
- superqode/agents/client.py +249 -0
- superqode/agents/data/augmentcode.com.toml +51 -0
- superqode/agents/data/cagent.dev.toml +51 -0
- superqode/agents/data/claude.com.toml +60 -0
- superqode/agents/data/codeassistant.dev.toml +51 -0
- superqode/agents/data/codex.openai.com.toml +57 -0
- superqode/agents/data/fastagent.ai.toml +66 -0
- superqode/agents/data/geminicli.com.toml +77 -0
- superqode/agents/data/goose.block.xyz.toml +54 -0
- superqode/agents/data/junie.jetbrains.com.toml +56 -0
- superqode/agents/data/kimi.moonshot.cn.toml +57 -0
- superqode/agents/data/llmlingagent.dev.toml +51 -0
- superqode/agents/data/molt.bot.toml +49 -0
- superqode/agents/data/opencode.ai.toml +60 -0
- superqode/agents/data/stakpak.dev.toml +51 -0
- superqode/agents/data/vtcode.dev.toml +51 -0
- superqode/agents/discovery.py +266 -0
- superqode/agents/messaging.py +160 -0
- superqode/agents/persona.py +166 -0
- superqode/agents/registry.py +421 -0
- superqode/agents/schema.py +72 -0
- superqode/agents/unified.py +367 -0
- superqode/app/__init__.py +111 -0
- superqode/app/constants.py +314 -0
- superqode/app/css.py +366 -0
- superqode/app/models.py +118 -0
- superqode/app/suggester.py +125 -0
- superqode/app/widgets.py +1591 -0
- superqode/app_enhanced.py +399 -0
- superqode/app_main.py +17187 -0
- superqode/approval.py +312 -0
- superqode/atomic.py +296 -0
- superqode/commands/__init__.py +1 -0
- superqode/commands/acp.py +965 -0
- superqode/commands/agents.py +180 -0
- superqode/commands/auth.py +278 -0
- superqode/commands/config.py +374 -0
- superqode/commands/init.py +826 -0
- superqode/commands/providers.py +819 -0
- superqode/commands/qe.py +1145 -0
- superqode/commands/roles.py +380 -0
- superqode/commands/serve.py +172 -0
- superqode/commands/suggestions.py +127 -0
- superqode/commands/superqe.py +460 -0
- superqode/config/__init__.py +51 -0
- superqode/config/loader.py +812 -0
- superqode/config/schema.py +498 -0
- superqode/core/__init__.py +111 -0
- superqode/core/roles.py +281 -0
- superqode/danger.py +386 -0
- superqode/data/superqode-template.yaml +1522 -0
- superqode/design_system.py +1080 -0
- superqode/dialogs/__init__.py +6 -0
- superqode/dialogs/base.py +39 -0
- superqode/dialogs/model.py +130 -0
- superqode/dialogs/provider.py +870 -0
- superqode/diff_view.py +919 -0
- superqode/enterprise.py +21 -0
- superqode/evaluation/__init__.py +25 -0
- superqode/evaluation/adapters.py +93 -0
- superqode/evaluation/behaviors.py +89 -0
- superqode/evaluation/engine.py +209 -0
- superqode/evaluation/scenarios.py +96 -0
- superqode/execution/__init__.py +36 -0
- superqode/execution/linter.py +538 -0
- superqode/execution/modes.py +347 -0
- superqode/execution/resolver.py +283 -0
- superqode/execution/runner.py +642 -0
- superqode/file_explorer.py +811 -0
- superqode/file_viewer.py +471 -0
- superqode/flash.py +183 -0
- superqode/guidance/__init__.py +58 -0
- superqode/guidance/config.py +203 -0
- superqode/guidance/prompts.py +71 -0
- superqode/harness/__init__.py +54 -0
- superqode/harness/accelerator.py +291 -0
- superqode/harness/config.py +319 -0
- superqode/harness/validator.py +147 -0
- superqode/history.py +279 -0
- superqode/integrations/superopt_runner.py +124 -0
- superqode/logging/__init__.py +49 -0
- superqode/logging/adapters.py +219 -0
- superqode/logging/formatter.py +923 -0
- superqode/logging/integration.py +341 -0
- superqode/logging/sinks.py +170 -0
- superqode/logging/unified_log.py +417 -0
- superqode/lsp/__init__.py +26 -0
- superqode/lsp/client.py +544 -0
- superqode/main.py +1069 -0
- superqode/mcp/__init__.py +89 -0
- superqode/mcp/auth_storage.py +380 -0
- superqode/mcp/client.py +1236 -0
- superqode/mcp/config.py +319 -0
- superqode/mcp/integration.py +337 -0
- superqode/mcp/oauth.py +436 -0
- superqode/mcp/oauth_callback.py +385 -0
- superqode/mcp/types.py +290 -0
- superqode/memory/__init__.py +31 -0
- superqode/memory/feedback.py +342 -0
- superqode/memory/store.py +522 -0
- superqode/notifications.py +369 -0
- superqode/optimization/__init__.py +5 -0
- superqode/optimization/config.py +33 -0
- superqode/permissions/__init__.py +25 -0
- superqode/permissions/rules.py +488 -0
- superqode/plan.py +323 -0
- superqode/providers/__init__.py +33 -0
- superqode/providers/gateway/__init__.py +165 -0
- superqode/providers/gateway/base.py +228 -0
- superqode/providers/gateway/litellm_gateway.py +1170 -0
- superqode/providers/gateway/openresponses_gateway.py +436 -0
- superqode/providers/health.py +297 -0
- superqode/providers/huggingface/__init__.py +74 -0
- superqode/providers/huggingface/downloader.py +472 -0
- superqode/providers/huggingface/endpoints.py +442 -0
- superqode/providers/huggingface/hub.py +531 -0
- superqode/providers/huggingface/inference.py +394 -0
- superqode/providers/huggingface/transformers_runner.py +516 -0
- superqode/providers/local/__init__.py +100 -0
- superqode/providers/local/base.py +438 -0
- superqode/providers/local/discovery.py +418 -0
- superqode/providers/local/lmstudio.py +256 -0
- superqode/providers/local/mlx.py +457 -0
- superqode/providers/local/ollama.py +486 -0
- superqode/providers/local/sglang.py +268 -0
- superqode/providers/local/tgi.py +260 -0
- superqode/providers/local/tool_support.py +477 -0
- superqode/providers/local/vllm.py +258 -0
- superqode/providers/manager.py +1338 -0
- superqode/providers/models.py +1016 -0
- superqode/providers/models_dev.py +578 -0
- superqode/providers/openresponses/__init__.py +87 -0
- superqode/providers/openresponses/converters/__init__.py +17 -0
- superqode/providers/openresponses/converters/messages.py +343 -0
- superqode/providers/openresponses/converters/tools.py +268 -0
- superqode/providers/openresponses/schema/__init__.py +56 -0
- superqode/providers/openresponses/schema/models.py +585 -0
- superqode/providers/openresponses/streaming/__init__.py +5 -0
- superqode/providers/openresponses/streaming/parser.py +338 -0
- superqode/providers/openresponses/tools/__init__.py +21 -0
- superqode/providers/openresponses/tools/apply_patch.py +352 -0
- superqode/providers/openresponses/tools/code_interpreter.py +290 -0
- superqode/providers/openresponses/tools/file_search.py +333 -0
- superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
- superqode/providers/registry.py +716 -0
- superqode/providers/usage.py +332 -0
- superqode/pure_mode.py +384 -0
- superqode/qr/__init__.py +23 -0
- superqode/qr/dashboard.py +781 -0
- superqode/qr/generator.py +1018 -0
- superqode/qr/templates.py +135 -0
- superqode/safety/__init__.py +41 -0
- superqode/safety/sandbox.py +413 -0
- superqode/safety/warnings.py +256 -0
- superqode/server/__init__.py +33 -0
- superqode/server/lsp_server.py +775 -0
- superqode/server/web.py +250 -0
- superqode/session/__init__.py +25 -0
- superqode/session/persistence.py +580 -0
- superqode/session/sharing.py +477 -0
- superqode/session.py +475 -0
- superqode/sidebar.py +2991 -0
- superqode/stream_view.py +648 -0
- superqode/styles/__init__.py +3 -0
- superqode/superqe/__init__.py +184 -0
- superqode/superqe/acp_runner.py +1064 -0
- superqode/superqe/constitution/__init__.py +62 -0
- superqode/superqe/constitution/evaluator.py +308 -0
- superqode/superqe/constitution/loader.py +432 -0
- superqode/superqe/constitution/schema.py +250 -0
- superqode/superqe/events.py +591 -0
- superqode/superqe/frameworks/__init__.py +65 -0
- superqode/superqe/frameworks/base.py +234 -0
- superqode/superqe/frameworks/e2e.py +263 -0
- superqode/superqe/frameworks/executor.py +237 -0
- superqode/superqe/frameworks/javascript.py +409 -0
- superqode/superqe/frameworks/python.py +373 -0
- superqode/superqe/frameworks/registry.py +92 -0
- superqode/superqe/mcp_tools/__init__.py +47 -0
- superqode/superqe/mcp_tools/core_tools.py +418 -0
- superqode/superqe/mcp_tools/registry.py +230 -0
- superqode/superqe/mcp_tools/testing_tools.py +167 -0
- superqode/superqe/noise.py +89 -0
- superqode/superqe/orchestrator.py +778 -0
- superqode/superqe/roles.py +609 -0
- superqode/superqe/session.py +713 -0
- superqode/superqe/skills/__init__.py +57 -0
- superqode/superqe/skills/base.py +106 -0
- superqode/superqe/skills/core_skills.py +899 -0
- superqode/superqe/skills/registry.py +90 -0
- superqode/superqe/verifier.py +101 -0
- superqode/superqe_cli.py +76 -0
- superqode/tool_call.py +358 -0
- superqode/tools/__init__.py +93 -0
- superqode/tools/agent_tools.py +496 -0
- superqode/tools/base.py +324 -0
- superqode/tools/batch_tool.py +133 -0
- superqode/tools/diagnostics.py +311 -0
- superqode/tools/edit_tools.py +653 -0
- superqode/tools/enhanced_base.py +515 -0
- superqode/tools/file_tools.py +269 -0
- superqode/tools/file_tracking.py +45 -0
- superqode/tools/lsp_tools.py +610 -0
- superqode/tools/network_tools.py +350 -0
- superqode/tools/permissions.py +400 -0
- superqode/tools/question_tool.py +324 -0
- superqode/tools/search_tools.py +598 -0
- superqode/tools/shell_tools.py +259 -0
- superqode/tools/todo_tools.py +121 -0
- superqode/tools/validation.py +80 -0
- superqode/tools/web_tools.py +639 -0
- superqode/tui.py +1152 -0
- superqode/tui_integration.py +875 -0
- superqode/tui_widgets/__init__.py +27 -0
- superqode/tui_widgets/widgets/__init__.py +18 -0
- superqode/tui_widgets/widgets/progress.py +185 -0
- superqode/tui_widgets/widgets/tool_display.py +188 -0
- superqode/undo_manager.py +574 -0
- superqode/utils/__init__.py +5 -0
- superqode/utils/error_handling.py +323 -0
- superqode/utils/fuzzy.py +257 -0
- superqode/widgets/__init__.py +477 -0
- superqode/widgets/agent_collab.py +390 -0
- superqode/widgets/agent_store.py +936 -0
- superqode/widgets/agent_switcher.py +395 -0
- superqode/widgets/animation_manager.py +284 -0
- superqode/widgets/code_context.py +356 -0
- superqode/widgets/command_palette.py +412 -0
- superqode/widgets/connection_status.py +537 -0
- superqode/widgets/conversation_history.py +470 -0
- superqode/widgets/diff_indicator.py +155 -0
- superqode/widgets/enhanced_status_bar.py +385 -0
- superqode/widgets/enhanced_toast.py +476 -0
- superqode/widgets/file_browser.py +809 -0
- superqode/widgets/file_reference.py +585 -0
- superqode/widgets/issue_timeline.py +340 -0
- superqode/widgets/leader_key.py +264 -0
- superqode/widgets/mode_switcher.py +445 -0
- superqode/widgets/model_picker.py +234 -0
- superqode/widgets/permission_preview.py +1205 -0
- superqode/widgets/prompt.py +358 -0
- superqode/widgets/provider_connect.py +725 -0
- superqode/widgets/pty_shell.py +587 -0
- superqode/widgets/qe_dashboard.py +321 -0
- superqode/widgets/resizable_sidebar.py +377 -0
- superqode/widgets/response_changes.py +218 -0
- superqode/widgets/response_display.py +528 -0
- superqode/widgets/rich_tool_display.py +613 -0
- superqode/widgets/sidebar_panels.py +1180 -0
- superqode/widgets/slash_complete.py +356 -0
- superqode/widgets/split_view.py +612 -0
- superqode/widgets/status_bar.py +273 -0
- superqode/widgets/superqode_display.py +786 -0
- superqode/widgets/thinking_display.py +815 -0
- superqode/widgets/throbber.py +87 -0
- superqode/widgets/toast.py +206 -0
- superqode/widgets/unified_output.py +1073 -0
- superqode/workspace/__init__.py +75 -0
- superqode/workspace/artifacts.py +472 -0
- superqode/workspace/coordinator.py +353 -0
- superqode/workspace/diff_tracker.py +429 -0
- superqode/workspace/git_guard.py +373 -0
- superqode/workspace/git_snapshot.py +526 -0
- superqode/workspace/manager.py +750 -0
- superqode/workspace/snapshot.py +357 -0
- superqode/workspace/watcher.py +535 -0
- superqode/workspace/worktree.py +440 -0
- superqode-0.1.5.dist-info/METADATA +204 -0
- superqode-0.1.5.dist-info/RECORD +288 -0
- superqode-0.1.5.dist-info/WHEEL +5 -0
- superqode-0.1.5.dist-info/entry_points.txt +3 -0
- superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
- superqode-0.1.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
"""
|
|
2
|
+
File Search Tool for Open Responses.
|
|
3
|
+
|
|
4
|
+
Implements file search functionality for the workspace.
|
|
5
|
+
Provides both text-based and semantic search capabilities.
|
|
6
|
+
|
|
7
|
+
Features:
|
|
8
|
+
- Full-text search with ripgrep
|
|
9
|
+
- Glob pattern matching
|
|
10
|
+
- File content indexing
|
|
11
|
+
- Result ranking
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
tool = FileSearchTool(workspace_root="/path/to/project")
|
|
15
|
+
results = await tool.search("error handling")
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import asyncio
|
|
21
|
+
import os
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Any, Dict, List, Optional
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class SearchResult:
|
|
29
|
+
"""A single search result."""
|
|
30
|
+
|
|
31
|
+
file_path: str
|
|
32
|
+
line_number: int
|
|
33
|
+
content: str
|
|
34
|
+
score: float = 1.0
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class SearchResults:
|
|
39
|
+
"""Collection of search results."""
|
|
40
|
+
|
|
41
|
+
results: List[SearchResult] = field(default_factory=list)
|
|
42
|
+
total_matches: int = 0
|
|
43
|
+
truncated: bool = False
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class FileSearchTool:
|
|
47
|
+
"""
|
|
48
|
+
File search tool for Open Responses.
|
|
49
|
+
|
|
50
|
+
Provides text-based file search using ripgrep or grep.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
workspace_root: Root directory for search
|
|
54
|
+
max_results: Maximum number of results
|
|
55
|
+
context_lines: Lines of context around matches
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
workspace_root: str,
|
|
61
|
+
max_results: int = 20,
|
|
62
|
+
context_lines: int = 2,
|
|
63
|
+
):
|
|
64
|
+
self.workspace_root = Path(workspace_root).resolve()
|
|
65
|
+
self.max_results = max_results
|
|
66
|
+
self.context_lines = context_lines
|
|
67
|
+
|
|
68
|
+
async def search(
|
|
69
|
+
self,
|
|
70
|
+
query: str,
|
|
71
|
+
file_pattern: Optional[str] = None,
|
|
72
|
+
max_results: Optional[int] = None,
|
|
73
|
+
) -> Dict[str, Any]:
|
|
74
|
+
"""
|
|
75
|
+
Search for text in workspace files.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
query: Search query (supports regex)
|
|
79
|
+
file_pattern: Optional glob pattern to filter files
|
|
80
|
+
max_results: Override default max results
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Dict with search results and metadata
|
|
84
|
+
"""
|
|
85
|
+
use_max_results = max_results if max_results is not None else self.max_results
|
|
86
|
+
|
|
87
|
+
# Try ripgrep first, fall back to grep
|
|
88
|
+
try:
|
|
89
|
+
results = await self._search_ripgrep(query, file_pattern, use_max_results)
|
|
90
|
+
except FileNotFoundError:
|
|
91
|
+
results = await self._search_grep(query, file_pattern, use_max_results)
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
"success": True,
|
|
95
|
+
"results": [
|
|
96
|
+
{
|
|
97
|
+
"file": r.file_path,
|
|
98
|
+
"line": r.line_number,
|
|
99
|
+
"content": r.content,
|
|
100
|
+
"score": r.score,
|
|
101
|
+
}
|
|
102
|
+
for r in results.results
|
|
103
|
+
],
|
|
104
|
+
"total_matches": results.total_matches,
|
|
105
|
+
"truncated": results.truncated,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async def find_files(
|
|
109
|
+
self,
|
|
110
|
+
pattern: str,
|
|
111
|
+
max_results: Optional[int] = None,
|
|
112
|
+
) -> Dict[str, Any]:
|
|
113
|
+
"""
|
|
114
|
+
Find files matching a glob pattern.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
pattern: Glob pattern (e.g., "**/*.py")
|
|
118
|
+
max_results: Override default max results
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Dict with matching file paths
|
|
122
|
+
"""
|
|
123
|
+
use_max_results = max_results if max_results is not None else self.max_results
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
# Use glob to find files
|
|
127
|
+
matches = list(self.workspace_root.glob(pattern))
|
|
128
|
+
truncated = len(matches) > use_max_results
|
|
129
|
+
|
|
130
|
+
# Sort by modification time (newest first)
|
|
131
|
+
matches.sort(key=lambda p: p.stat().st_mtime, reverse=True)
|
|
132
|
+
matches = matches[:use_max_results]
|
|
133
|
+
|
|
134
|
+
# Convert to relative paths
|
|
135
|
+
file_paths = [str(p.relative_to(self.workspace_root)) for p in matches if p.is_file()]
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
"success": True,
|
|
139
|
+
"files": file_paths,
|
|
140
|
+
"total_matches": len(file_paths),
|
|
141
|
+
"truncated": truncated,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
except Exception as e:
|
|
145
|
+
return {
|
|
146
|
+
"success": False,
|
|
147
|
+
"error": str(e),
|
|
148
|
+
"files": [],
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async def read_file(
|
|
152
|
+
self,
|
|
153
|
+
file_path: str,
|
|
154
|
+
start_line: Optional[int] = None,
|
|
155
|
+
end_line: Optional[int] = None,
|
|
156
|
+
) -> Dict[str, Any]:
|
|
157
|
+
"""
|
|
158
|
+
Read file content.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
file_path: Path to file (relative to workspace)
|
|
162
|
+
start_line: Optional start line (1-indexed)
|
|
163
|
+
end_line: Optional end line (1-indexed)
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Dict with file content
|
|
167
|
+
"""
|
|
168
|
+
full_path = self.workspace_root / file_path
|
|
169
|
+
|
|
170
|
+
if not full_path.exists():
|
|
171
|
+
return {
|
|
172
|
+
"success": False,
|
|
173
|
+
"error": f"File not found: {file_path}",
|
|
174
|
+
"content": "",
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
content = full_path.read_text(encoding="utf-8", errors="replace")
|
|
179
|
+
lines = content.split("\n")
|
|
180
|
+
|
|
181
|
+
# Apply line range if specified
|
|
182
|
+
if start_line is not None or end_line is not None:
|
|
183
|
+
start = (start_line - 1) if start_line else 0
|
|
184
|
+
end = end_line if end_line else len(lines)
|
|
185
|
+
lines = lines[start:end]
|
|
186
|
+
content = "\n".join(lines)
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
"success": True,
|
|
190
|
+
"content": content,
|
|
191
|
+
"line_count": len(lines),
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
except Exception as e:
|
|
195
|
+
return {
|
|
196
|
+
"success": False,
|
|
197
|
+
"error": str(e),
|
|
198
|
+
"content": "",
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async def _search_ripgrep(
|
|
202
|
+
self,
|
|
203
|
+
query: str,
|
|
204
|
+
file_pattern: Optional[str],
|
|
205
|
+
max_results: int,
|
|
206
|
+
) -> SearchResults:
|
|
207
|
+
"""Search using ripgrep (rg)."""
|
|
208
|
+
cmd = [
|
|
209
|
+
"rg",
|
|
210
|
+
"--json",
|
|
211
|
+
"--max-count",
|
|
212
|
+
str(max_results * 2), # Over-fetch for filtering
|
|
213
|
+
"-C",
|
|
214
|
+
str(self.context_lines),
|
|
215
|
+
]
|
|
216
|
+
|
|
217
|
+
if file_pattern:
|
|
218
|
+
cmd.extend(["--glob", file_pattern])
|
|
219
|
+
|
|
220
|
+
cmd.extend([query, str(self.workspace_root)])
|
|
221
|
+
|
|
222
|
+
proc = await asyncio.create_subprocess_exec(
|
|
223
|
+
*cmd,
|
|
224
|
+
stdout=asyncio.subprocess.PIPE,
|
|
225
|
+
stderr=asyncio.subprocess.PIPE,
|
|
226
|
+
)
|
|
227
|
+
stdout, _ = await proc.communicate()
|
|
228
|
+
|
|
229
|
+
results = []
|
|
230
|
+
total_matches = 0
|
|
231
|
+
|
|
232
|
+
import json
|
|
233
|
+
|
|
234
|
+
for line in stdout.decode("utf-8").split("\n"):
|
|
235
|
+
if not line.strip():
|
|
236
|
+
continue
|
|
237
|
+
|
|
238
|
+
try:
|
|
239
|
+
data = json.loads(line)
|
|
240
|
+
if data.get("type") == "match":
|
|
241
|
+
match_data = data.get("data", {})
|
|
242
|
+
path = match_data.get("path", {}).get("text", "")
|
|
243
|
+
line_num = match_data.get("line_number", 0)
|
|
244
|
+
lines = match_data.get("lines", {}).get("text", "")
|
|
245
|
+
|
|
246
|
+
# Make path relative
|
|
247
|
+
try:
|
|
248
|
+
rel_path = str(Path(path).relative_to(self.workspace_root))
|
|
249
|
+
except ValueError:
|
|
250
|
+
rel_path = path
|
|
251
|
+
|
|
252
|
+
results.append(
|
|
253
|
+
SearchResult(
|
|
254
|
+
file_path=rel_path,
|
|
255
|
+
line_number=line_num,
|
|
256
|
+
content=lines.strip(),
|
|
257
|
+
)
|
|
258
|
+
)
|
|
259
|
+
total_matches += 1
|
|
260
|
+
|
|
261
|
+
if len(results) >= max_results:
|
|
262
|
+
break
|
|
263
|
+
|
|
264
|
+
except json.JSONDecodeError:
|
|
265
|
+
continue
|
|
266
|
+
|
|
267
|
+
return SearchResults(
|
|
268
|
+
results=results,
|
|
269
|
+
total_matches=total_matches,
|
|
270
|
+
truncated=total_matches > max_results,
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
async def _search_grep(
|
|
274
|
+
self,
|
|
275
|
+
query: str,
|
|
276
|
+
file_pattern: Optional[str],
|
|
277
|
+
max_results: int,
|
|
278
|
+
) -> SearchResults:
|
|
279
|
+
"""Search using grep (fallback)."""
|
|
280
|
+
cmd = f"grep -rn --include='{file_pattern or '*'}' '{query}' {self.workspace_root}"
|
|
281
|
+
|
|
282
|
+
proc = await asyncio.create_subprocess_shell(
|
|
283
|
+
cmd,
|
|
284
|
+
stdout=asyncio.subprocess.PIPE,
|
|
285
|
+
stderr=asyncio.subprocess.PIPE,
|
|
286
|
+
)
|
|
287
|
+
stdout, _ = await proc.communicate()
|
|
288
|
+
|
|
289
|
+
results = []
|
|
290
|
+
total_matches = 0
|
|
291
|
+
|
|
292
|
+
for line in stdout.decode("utf-8", errors="replace").split("\n"):
|
|
293
|
+
if not line.strip():
|
|
294
|
+
continue
|
|
295
|
+
|
|
296
|
+
# Parse grep output: file:line:content
|
|
297
|
+
parts = line.split(":", 2)
|
|
298
|
+
if len(parts) >= 3:
|
|
299
|
+
file_path, line_num, content = parts[0], parts[1], parts[2]
|
|
300
|
+
|
|
301
|
+
# Make path relative
|
|
302
|
+
try:
|
|
303
|
+
rel_path = str(Path(file_path).relative_to(self.workspace_root))
|
|
304
|
+
except ValueError:
|
|
305
|
+
rel_path = file_path
|
|
306
|
+
|
|
307
|
+
try:
|
|
308
|
+
results.append(
|
|
309
|
+
SearchResult(
|
|
310
|
+
file_path=rel_path,
|
|
311
|
+
line_number=int(line_num),
|
|
312
|
+
content=content.strip(),
|
|
313
|
+
)
|
|
314
|
+
)
|
|
315
|
+
total_matches += 1
|
|
316
|
+
|
|
317
|
+
if len(results) >= max_results:
|
|
318
|
+
break
|
|
319
|
+
except ValueError:
|
|
320
|
+
continue
|
|
321
|
+
|
|
322
|
+
return SearchResults(
|
|
323
|
+
results=results,
|
|
324
|
+
total_matches=total_matches,
|
|
325
|
+
truncated=total_matches > max_results,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
def get_tool_definition(self) -> Dict[str, Any]:
|
|
329
|
+
"""Get the Open Responses tool definition for file_search."""
|
|
330
|
+
return {
|
|
331
|
+
"type": "file_search",
|
|
332
|
+
"max_num_results": self.max_results,
|
|
333
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Tool Adapter for Open Responses.
|
|
3
|
+
|
|
4
|
+
Adapts MCP (Model Context Protocol) tools for use with Open Responses.
|
|
5
|
+
Provides bidirectional conversion between MCP and Open Responses tool formats.
|
|
6
|
+
|
|
7
|
+
Features:
|
|
8
|
+
- MCP tool discovery
|
|
9
|
+
- Tool execution wrapping
|
|
10
|
+
- Result format conversion
|
|
11
|
+
- Server management
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
adapter = MCPToolAdapter()
|
|
15
|
+
adapter.register_mcp_server("filesystem", server)
|
|
16
|
+
tools = adapter.get_openresponses_tools()
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from typing import Any, Callable, Dict, List, Optional, Awaitable
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class MCPTool:
|
|
27
|
+
"""An MCP tool definition."""
|
|
28
|
+
|
|
29
|
+
name: str
|
|
30
|
+
description: str
|
|
31
|
+
input_schema: Dict[str, Any]
|
|
32
|
+
server_name: str = ""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class MCPServer:
|
|
37
|
+
"""An MCP server reference."""
|
|
38
|
+
|
|
39
|
+
name: str
|
|
40
|
+
tools: List[MCPTool] = field(default_factory=list)
|
|
41
|
+
execute_fn: Optional[Callable[[str, Dict[str, Any]], Awaitable[Any]]] = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class MCPToolAdapter:
|
|
45
|
+
"""
|
|
46
|
+
Adapter for MCP tools in Open Responses.
|
|
47
|
+
|
|
48
|
+
Bridges MCP tool servers with Open Responses tool format,
|
|
49
|
+
enabling seamless integration of MCP capabilities.
|
|
50
|
+
|
|
51
|
+
Usage:
|
|
52
|
+
adapter = MCPToolAdapter()
|
|
53
|
+
|
|
54
|
+
# Register an MCP server
|
|
55
|
+
adapter.register_server("filesystem", filesystem_server)
|
|
56
|
+
|
|
57
|
+
# Get tools in Open Responses format
|
|
58
|
+
tools = adapter.get_openresponses_tools()
|
|
59
|
+
|
|
60
|
+
# Execute a tool call
|
|
61
|
+
result = await adapter.execute_tool("filesystem__read_file", {"path": "test.py"})
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(self):
|
|
65
|
+
self._servers: Dict[str, MCPServer] = {}
|
|
66
|
+
self._tool_map: Dict[str, MCPTool] = {}
|
|
67
|
+
|
|
68
|
+
def register_server(
|
|
69
|
+
self,
|
|
70
|
+
name: str,
|
|
71
|
+
tools: List[Dict[str, Any]],
|
|
72
|
+
execute_fn: Optional[Callable[[str, Dict[str, Any]], Awaitable[Any]]] = None,
|
|
73
|
+
) -> None:
|
|
74
|
+
"""
|
|
75
|
+
Register an MCP server.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
name: Server name
|
|
79
|
+
tools: List of tool definitions from MCP server
|
|
80
|
+
execute_fn: Function to execute tools on this server
|
|
81
|
+
"""
|
|
82
|
+
mcp_tools = []
|
|
83
|
+
for tool_def in tools:
|
|
84
|
+
tool = MCPTool(
|
|
85
|
+
name=tool_def.get("name", ""),
|
|
86
|
+
description=tool_def.get("description", ""),
|
|
87
|
+
input_schema=tool_def.get("inputSchema", {}),
|
|
88
|
+
server_name=name,
|
|
89
|
+
)
|
|
90
|
+
mcp_tools.append(tool)
|
|
91
|
+
|
|
92
|
+
# Register in tool map with namespaced name
|
|
93
|
+
namespaced_name = f"{name}__{tool.name}"
|
|
94
|
+
self._tool_map[namespaced_name] = tool
|
|
95
|
+
|
|
96
|
+
self._servers[name] = MCPServer(
|
|
97
|
+
name=name,
|
|
98
|
+
tools=mcp_tools,
|
|
99
|
+
execute_fn=execute_fn,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def unregister_server(self, name: str) -> None:
|
|
103
|
+
"""Unregister an MCP server."""
|
|
104
|
+
if name in self._servers:
|
|
105
|
+
server = self._servers[name]
|
|
106
|
+
for tool in server.tools:
|
|
107
|
+
namespaced_name = f"{name}__{tool.name}"
|
|
108
|
+
if namespaced_name in self._tool_map:
|
|
109
|
+
del self._tool_map[namespaced_name]
|
|
110
|
+
del self._servers[name]
|
|
111
|
+
|
|
112
|
+
def get_openresponses_tools(self) -> List[Dict[str, Any]]:
|
|
113
|
+
"""
|
|
114
|
+
Get all registered MCP tools in Open Responses format.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
List of Open Responses tool definitions
|
|
118
|
+
"""
|
|
119
|
+
tools = []
|
|
120
|
+
for namespaced_name, tool in self._tool_map.items():
|
|
121
|
+
tools.append(
|
|
122
|
+
{
|
|
123
|
+
"type": "function",
|
|
124
|
+
"function": {
|
|
125
|
+
"name": namespaced_name,
|
|
126
|
+
"description": f"[{tool.server_name}] {tool.description}",
|
|
127
|
+
"parameters": tool.input_schema,
|
|
128
|
+
},
|
|
129
|
+
}
|
|
130
|
+
)
|
|
131
|
+
return tools
|
|
132
|
+
|
|
133
|
+
def get_mcp_tools(self, server_name: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
134
|
+
"""
|
|
135
|
+
Get tools in MCP format.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
server_name: Optional filter by server
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
List of MCP tool definitions
|
|
142
|
+
"""
|
|
143
|
+
tools = []
|
|
144
|
+
for tool in self._tool_map.values():
|
|
145
|
+
if server_name and tool.server_name != server_name:
|
|
146
|
+
continue
|
|
147
|
+
tools.append(
|
|
148
|
+
{
|
|
149
|
+
"name": tool.name,
|
|
150
|
+
"description": tool.description,
|
|
151
|
+
"inputSchema": tool.input_schema,
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
return tools
|
|
155
|
+
|
|
156
|
+
async def execute_tool(
|
|
157
|
+
self,
|
|
158
|
+
tool_name: str,
|
|
159
|
+
arguments: Dict[str, Any],
|
|
160
|
+
) -> Dict[str, Any]:
|
|
161
|
+
"""
|
|
162
|
+
Execute a tool call.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
tool_name: Namespaced tool name (server__tool)
|
|
166
|
+
arguments: Tool arguments
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
Tool execution result
|
|
170
|
+
"""
|
|
171
|
+
if tool_name not in self._tool_map:
|
|
172
|
+
return {
|
|
173
|
+
"success": False,
|
|
174
|
+
"error": f"Unknown tool: {tool_name}",
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
tool = self._tool_map[tool_name]
|
|
178
|
+
server = self._servers.get(tool.server_name)
|
|
179
|
+
|
|
180
|
+
if not server or not server.execute_fn:
|
|
181
|
+
return {
|
|
182
|
+
"success": False,
|
|
183
|
+
"error": f"Server not available: {tool.server_name}",
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
result = await server.execute_fn(tool.name, arguments)
|
|
188
|
+
return {
|
|
189
|
+
"success": True,
|
|
190
|
+
"result": result,
|
|
191
|
+
}
|
|
192
|
+
except Exception as e:
|
|
193
|
+
return {
|
|
194
|
+
"success": False,
|
|
195
|
+
"error": str(e),
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
def parse_tool_call(
|
|
199
|
+
self,
|
|
200
|
+
tool_name: str,
|
|
201
|
+
) -> Optional[tuple[str, str]]:
|
|
202
|
+
"""
|
|
203
|
+
Parse a namespaced tool name into server and tool names.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
tool_name: Namespaced tool name
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Tuple of (server_name, tool_name) or None
|
|
210
|
+
"""
|
|
211
|
+
if "__" in tool_name:
|
|
212
|
+
parts = tool_name.split("__", 1)
|
|
213
|
+
return (parts[0], parts[1])
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
def get_server_names(self) -> List[str]:
|
|
217
|
+
"""Get all registered server names."""
|
|
218
|
+
return list(self._servers.keys())
|
|
219
|
+
|
|
220
|
+
def get_server_tools(self, server_name: str) -> List[MCPTool]:
|
|
221
|
+
"""Get tools for a specific server."""
|
|
222
|
+
server = self._servers.get(server_name)
|
|
223
|
+
return server.tools if server else []
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def create_mcp_tool_filter(
|
|
227
|
+
allowed_servers: Optional[List[str]] = None,
|
|
228
|
+
allowed_tools: Optional[List[str]] = None,
|
|
229
|
+
blocked_tools: Optional[List[str]] = None,
|
|
230
|
+
) -> Callable[[MCPTool], bool]:
|
|
231
|
+
"""
|
|
232
|
+
Create a filter function for MCP tools.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
allowed_servers: Only include tools from these servers
|
|
236
|
+
allowed_tools: Only include these specific tools
|
|
237
|
+
blocked_tools: Exclude these specific tools
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
Filter function
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
def filter_fn(tool: MCPTool) -> bool:
|
|
244
|
+
if allowed_servers and tool.server_name not in allowed_servers:
|
|
245
|
+
return False
|
|
246
|
+
if blocked_tools and tool.name in blocked_tools:
|
|
247
|
+
return False
|
|
248
|
+
if allowed_tools and tool.name not in allowed_tools:
|
|
249
|
+
return False
|
|
250
|
+
return True
|
|
251
|
+
|
|
252
|
+
return filter_fn
|