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.
Files changed (288) hide show
  1. superqode/__init__.py +33 -0
  2. superqode/acp/__init__.py +23 -0
  3. superqode/acp/client.py +913 -0
  4. superqode/acp/permission_screen.py +457 -0
  5. superqode/acp/types.py +480 -0
  6. superqode/acp_discovery.py +856 -0
  7. superqode/agent/__init__.py +22 -0
  8. superqode/agent/edit_strategies.py +334 -0
  9. superqode/agent/loop.py +892 -0
  10. superqode/agent/qe_report_templates.py +39 -0
  11. superqode/agent/system_prompts.py +353 -0
  12. superqode/agent_output.py +721 -0
  13. superqode/agent_stream.py +953 -0
  14. superqode/agents/__init__.py +59 -0
  15. superqode/agents/acp_registry.py +305 -0
  16. superqode/agents/client.py +249 -0
  17. superqode/agents/data/augmentcode.com.toml +51 -0
  18. superqode/agents/data/cagent.dev.toml +51 -0
  19. superqode/agents/data/claude.com.toml +60 -0
  20. superqode/agents/data/codeassistant.dev.toml +51 -0
  21. superqode/agents/data/codex.openai.com.toml +57 -0
  22. superqode/agents/data/fastagent.ai.toml +66 -0
  23. superqode/agents/data/geminicli.com.toml +77 -0
  24. superqode/agents/data/goose.block.xyz.toml +54 -0
  25. superqode/agents/data/junie.jetbrains.com.toml +56 -0
  26. superqode/agents/data/kimi.moonshot.cn.toml +57 -0
  27. superqode/agents/data/llmlingagent.dev.toml +51 -0
  28. superqode/agents/data/molt.bot.toml +49 -0
  29. superqode/agents/data/opencode.ai.toml +60 -0
  30. superqode/agents/data/stakpak.dev.toml +51 -0
  31. superqode/agents/data/vtcode.dev.toml +51 -0
  32. superqode/agents/discovery.py +266 -0
  33. superqode/agents/messaging.py +160 -0
  34. superqode/agents/persona.py +166 -0
  35. superqode/agents/registry.py +421 -0
  36. superqode/agents/schema.py +72 -0
  37. superqode/agents/unified.py +367 -0
  38. superqode/app/__init__.py +111 -0
  39. superqode/app/constants.py +314 -0
  40. superqode/app/css.py +366 -0
  41. superqode/app/models.py +118 -0
  42. superqode/app/suggester.py +125 -0
  43. superqode/app/widgets.py +1591 -0
  44. superqode/app_enhanced.py +399 -0
  45. superqode/app_main.py +17187 -0
  46. superqode/approval.py +312 -0
  47. superqode/atomic.py +296 -0
  48. superqode/commands/__init__.py +1 -0
  49. superqode/commands/acp.py +965 -0
  50. superqode/commands/agents.py +180 -0
  51. superqode/commands/auth.py +278 -0
  52. superqode/commands/config.py +374 -0
  53. superqode/commands/init.py +826 -0
  54. superqode/commands/providers.py +819 -0
  55. superqode/commands/qe.py +1145 -0
  56. superqode/commands/roles.py +380 -0
  57. superqode/commands/serve.py +172 -0
  58. superqode/commands/suggestions.py +127 -0
  59. superqode/commands/superqe.py +460 -0
  60. superqode/config/__init__.py +51 -0
  61. superqode/config/loader.py +812 -0
  62. superqode/config/schema.py +498 -0
  63. superqode/core/__init__.py +111 -0
  64. superqode/core/roles.py +281 -0
  65. superqode/danger.py +386 -0
  66. superqode/data/superqode-template.yaml +1522 -0
  67. superqode/design_system.py +1080 -0
  68. superqode/dialogs/__init__.py +6 -0
  69. superqode/dialogs/base.py +39 -0
  70. superqode/dialogs/model.py +130 -0
  71. superqode/dialogs/provider.py +870 -0
  72. superqode/diff_view.py +919 -0
  73. superqode/enterprise.py +21 -0
  74. superqode/evaluation/__init__.py +25 -0
  75. superqode/evaluation/adapters.py +93 -0
  76. superqode/evaluation/behaviors.py +89 -0
  77. superqode/evaluation/engine.py +209 -0
  78. superqode/evaluation/scenarios.py +96 -0
  79. superqode/execution/__init__.py +36 -0
  80. superqode/execution/linter.py +538 -0
  81. superqode/execution/modes.py +347 -0
  82. superqode/execution/resolver.py +283 -0
  83. superqode/execution/runner.py +642 -0
  84. superqode/file_explorer.py +811 -0
  85. superqode/file_viewer.py +471 -0
  86. superqode/flash.py +183 -0
  87. superqode/guidance/__init__.py +58 -0
  88. superqode/guidance/config.py +203 -0
  89. superqode/guidance/prompts.py +71 -0
  90. superqode/harness/__init__.py +54 -0
  91. superqode/harness/accelerator.py +291 -0
  92. superqode/harness/config.py +319 -0
  93. superqode/harness/validator.py +147 -0
  94. superqode/history.py +279 -0
  95. superqode/integrations/superopt_runner.py +124 -0
  96. superqode/logging/__init__.py +49 -0
  97. superqode/logging/adapters.py +219 -0
  98. superqode/logging/formatter.py +923 -0
  99. superqode/logging/integration.py +341 -0
  100. superqode/logging/sinks.py +170 -0
  101. superqode/logging/unified_log.py +417 -0
  102. superqode/lsp/__init__.py +26 -0
  103. superqode/lsp/client.py +544 -0
  104. superqode/main.py +1069 -0
  105. superqode/mcp/__init__.py +89 -0
  106. superqode/mcp/auth_storage.py +380 -0
  107. superqode/mcp/client.py +1236 -0
  108. superqode/mcp/config.py +319 -0
  109. superqode/mcp/integration.py +337 -0
  110. superqode/mcp/oauth.py +436 -0
  111. superqode/mcp/oauth_callback.py +385 -0
  112. superqode/mcp/types.py +290 -0
  113. superqode/memory/__init__.py +31 -0
  114. superqode/memory/feedback.py +342 -0
  115. superqode/memory/store.py +522 -0
  116. superqode/notifications.py +369 -0
  117. superqode/optimization/__init__.py +5 -0
  118. superqode/optimization/config.py +33 -0
  119. superqode/permissions/__init__.py +25 -0
  120. superqode/permissions/rules.py +488 -0
  121. superqode/plan.py +323 -0
  122. superqode/providers/__init__.py +33 -0
  123. superqode/providers/gateway/__init__.py +165 -0
  124. superqode/providers/gateway/base.py +228 -0
  125. superqode/providers/gateway/litellm_gateway.py +1170 -0
  126. superqode/providers/gateway/openresponses_gateway.py +436 -0
  127. superqode/providers/health.py +297 -0
  128. superqode/providers/huggingface/__init__.py +74 -0
  129. superqode/providers/huggingface/downloader.py +472 -0
  130. superqode/providers/huggingface/endpoints.py +442 -0
  131. superqode/providers/huggingface/hub.py +531 -0
  132. superqode/providers/huggingface/inference.py +394 -0
  133. superqode/providers/huggingface/transformers_runner.py +516 -0
  134. superqode/providers/local/__init__.py +100 -0
  135. superqode/providers/local/base.py +438 -0
  136. superqode/providers/local/discovery.py +418 -0
  137. superqode/providers/local/lmstudio.py +256 -0
  138. superqode/providers/local/mlx.py +457 -0
  139. superqode/providers/local/ollama.py +486 -0
  140. superqode/providers/local/sglang.py +268 -0
  141. superqode/providers/local/tgi.py +260 -0
  142. superqode/providers/local/tool_support.py +477 -0
  143. superqode/providers/local/vllm.py +258 -0
  144. superqode/providers/manager.py +1338 -0
  145. superqode/providers/models.py +1016 -0
  146. superqode/providers/models_dev.py +578 -0
  147. superqode/providers/openresponses/__init__.py +87 -0
  148. superqode/providers/openresponses/converters/__init__.py +17 -0
  149. superqode/providers/openresponses/converters/messages.py +343 -0
  150. superqode/providers/openresponses/converters/tools.py +268 -0
  151. superqode/providers/openresponses/schema/__init__.py +56 -0
  152. superqode/providers/openresponses/schema/models.py +585 -0
  153. superqode/providers/openresponses/streaming/__init__.py +5 -0
  154. superqode/providers/openresponses/streaming/parser.py +338 -0
  155. superqode/providers/openresponses/tools/__init__.py +21 -0
  156. superqode/providers/openresponses/tools/apply_patch.py +352 -0
  157. superqode/providers/openresponses/tools/code_interpreter.py +290 -0
  158. superqode/providers/openresponses/tools/file_search.py +333 -0
  159. superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
  160. superqode/providers/registry.py +716 -0
  161. superqode/providers/usage.py +332 -0
  162. superqode/pure_mode.py +384 -0
  163. superqode/qr/__init__.py +23 -0
  164. superqode/qr/dashboard.py +781 -0
  165. superqode/qr/generator.py +1018 -0
  166. superqode/qr/templates.py +135 -0
  167. superqode/safety/__init__.py +41 -0
  168. superqode/safety/sandbox.py +413 -0
  169. superqode/safety/warnings.py +256 -0
  170. superqode/server/__init__.py +33 -0
  171. superqode/server/lsp_server.py +775 -0
  172. superqode/server/web.py +250 -0
  173. superqode/session/__init__.py +25 -0
  174. superqode/session/persistence.py +580 -0
  175. superqode/session/sharing.py +477 -0
  176. superqode/session.py +475 -0
  177. superqode/sidebar.py +2991 -0
  178. superqode/stream_view.py +648 -0
  179. superqode/styles/__init__.py +3 -0
  180. superqode/superqe/__init__.py +184 -0
  181. superqode/superqe/acp_runner.py +1064 -0
  182. superqode/superqe/constitution/__init__.py +62 -0
  183. superqode/superqe/constitution/evaluator.py +308 -0
  184. superqode/superqe/constitution/loader.py +432 -0
  185. superqode/superqe/constitution/schema.py +250 -0
  186. superqode/superqe/events.py +591 -0
  187. superqode/superqe/frameworks/__init__.py +65 -0
  188. superqode/superqe/frameworks/base.py +234 -0
  189. superqode/superqe/frameworks/e2e.py +263 -0
  190. superqode/superqe/frameworks/executor.py +237 -0
  191. superqode/superqe/frameworks/javascript.py +409 -0
  192. superqode/superqe/frameworks/python.py +373 -0
  193. superqode/superqe/frameworks/registry.py +92 -0
  194. superqode/superqe/mcp_tools/__init__.py +47 -0
  195. superqode/superqe/mcp_tools/core_tools.py +418 -0
  196. superqode/superqe/mcp_tools/registry.py +230 -0
  197. superqode/superqe/mcp_tools/testing_tools.py +167 -0
  198. superqode/superqe/noise.py +89 -0
  199. superqode/superqe/orchestrator.py +778 -0
  200. superqode/superqe/roles.py +609 -0
  201. superqode/superqe/session.py +713 -0
  202. superqode/superqe/skills/__init__.py +57 -0
  203. superqode/superqe/skills/base.py +106 -0
  204. superqode/superqe/skills/core_skills.py +899 -0
  205. superqode/superqe/skills/registry.py +90 -0
  206. superqode/superqe/verifier.py +101 -0
  207. superqode/superqe_cli.py +76 -0
  208. superqode/tool_call.py +358 -0
  209. superqode/tools/__init__.py +93 -0
  210. superqode/tools/agent_tools.py +496 -0
  211. superqode/tools/base.py +324 -0
  212. superqode/tools/batch_tool.py +133 -0
  213. superqode/tools/diagnostics.py +311 -0
  214. superqode/tools/edit_tools.py +653 -0
  215. superqode/tools/enhanced_base.py +515 -0
  216. superqode/tools/file_tools.py +269 -0
  217. superqode/tools/file_tracking.py +45 -0
  218. superqode/tools/lsp_tools.py +610 -0
  219. superqode/tools/network_tools.py +350 -0
  220. superqode/tools/permissions.py +400 -0
  221. superqode/tools/question_tool.py +324 -0
  222. superqode/tools/search_tools.py +598 -0
  223. superqode/tools/shell_tools.py +259 -0
  224. superqode/tools/todo_tools.py +121 -0
  225. superqode/tools/validation.py +80 -0
  226. superqode/tools/web_tools.py +639 -0
  227. superqode/tui.py +1152 -0
  228. superqode/tui_integration.py +875 -0
  229. superqode/tui_widgets/__init__.py +27 -0
  230. superqode/tui_widgets/widgets/__init__.py +18 -0
  231. superqode/tui_widgets/widgets/progress.py +185 -0
  232. superqode/tui_widgets/widgets/tool_display.py +188 -0
  233. superqode/undo_manager.py +574 -0
  234. superqode/utils/__init__.py +5 -0
  235. superqode/utils/error_handling.py +323 -0
  236. superqode/utils/fuzzy.py +257 -0
  237. superqode/widgets/__init__.py +477 -0
  238. superqode/widgets/agent_collab.py +390 -0
  239. superqode/widgets/agent_store.py +936 -0
  240. superqode/widgets/agent_switcher.py +395 -0
  241. superqode/widgets/animation_manager.py +284 -0
  242. superqode/widgets/code_context.py +356 -0
  243. superqode/widgets/command_palette.py +412 -0
  244. superqode/widgets/connection_status.py +537 -0
  245. superqode/widgets/conversation_history.py +470 -0
  246. superqode/widgets/diff_indicator.py +155 -0
  247. superqode/widgets/enhanced_status_bar.py +385 -0
  248. superqode/widgets/enhanced_toast.py +476 -0
  249. superqode/widgets/file_browser.py +809 -0
  250. superqode/widgets/file_reference.py +585 -0
  251. superqode/widgets/issue_timeline.py +340 -0
  252. superqode/widgets/leader_key.py +264 -0
  253. superqode/widgets/mode_switcher.py +445 -0
  254. superqode/widgets/model_picker.py +234 -0
  255. superqode/widgets/permission_preview.py +1205 -0
  256. superqode/widgets/prompt.py +358 -0
  257. superqode/widgets/provider_connect.py +725 -0
  258. superqode/widgets/pty_shell.py +587 -0
  259. superqode/widgets/qe_dashboard.py +321 -0
  260. superqode/widgets/resizable_sidebar.py +377 -0
  261. superqode/widgets/response_changes.py +218 -0
  262. superqode/widgets/response_display.py +528 -0
  263. superqode/widgets/rich_tool_display.py +613 -0
  264. superqode/widgets/sidebar_panels.py +1180 -0
  265. superqode/widgets/slash_complete.py +356 -0
  266. superqode/widgets/split_view.py +612 -0
  267. superqode/widgets/status_bar.py +273 -0
  268. superqode/widgets/superqode_display.py +786 -0
  269. superqode/widgets/thinking_display.py +815 -0
  270. superqode/widgets/throbber.py +87 -0
  271. superqode/widgets/toast.py +206 -0
  272. superqode/widgets/unified_output.py +1073 -0
  273. superqode/workspace/__init__.py +75 -0
  274. superqode/workspace/artifacts.py +472 -0
  275. superqode/workspace/coordinator.py +353 -0
  276. superqode/workspace/diff_tracker.py +429 -0
  277. superqode/workspace/git_guard.py +373 -0
  278. superqode/workspace/git_snapshot.py +526 -0
  279. superqode/workspace/manager.py +750 -0
  280. superqode/workspace/snapshot.py +357 -0
  281. superqode/workspace/watcher.py +535 -0
  282. superqode/workspace/worktree.py +440 -0
  283. superqode-0.1.5.dist-info/METADATA +204 -0
  284. superqode-0.1.5.dist-info/RECORD +288 -0
  285. superqode-0.1.5.dist-info/WHEEL +5 -0
  286. superqode-0.1.5.dist-info/entry_points.txt +3 -0
  287. superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
  288. 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