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,350 @@
1
+ """
2
+ Network Tools - HTTP/Web Operations.
3
+
4
+ Provides tools for fetching content from URLs, making API requests,
5
+ and working with web resources.
6
+
7
+ Features:
8
+ - Fetch HTML/JSON/text from URLs
9
+ - Configurable timeouts and size limits
10
+ - HTML to text extraction
11
+ - JSON response parsing
12
+ """
13
+
14
+ import asyncio
15
+ import json
16
+ import ssl
17
+ import urllib.request
18
+ import urllib.error
19
+ from pathlib import Path
20
+ from typing import Any, Dict, Optional
21
+ from html.parser import HTMLParser
22
+ import re
23
+
24
+ from .base import Tool, ToolResult, ToolContext
25
+
26
+
27
+ class HTMLTextExtractor(HTMLParser):
28
+ """Extract text content from HTML."""
29
+
30
+ def __init__(self):
31
+ super().__init__()
32
+ self._text = []
33
+ self._skip_tags = {"script", "style", "head", "meta", "link"}
34
+ self._current_tag = None
35
+ self._in_skip = False
36
+
37
+ def handle_starttag(self, tag, attrs):
38
+ self._current_tag = tag.lower()
39
+ if self._current_tag in self._skip_tags:
40
+ self._in_skip = True
41
+
42
+ def handle_endtag(self, tag):
43
+ if tag.lower() in self._skip_tags:
44
+ self._in_skip = False
45
+ self._current_tag = None
46
+
47
+ def handle_data(self, data):
48
+ if not self._in_skip:
49
+ text = data.strip()
50
+ if text:
51
+ self._text.append(text)
52
+
53
+ def get_text(self) -> str:
54
+ return "\n".join(self._text)
55
+
56
+
57
+ class FetchTool(Tool):
58
+ """
59
+ Fetch content from URLs.
60
+
61
+ Supports:
62
+ - HTML pages (with text extraction)
63
+ - JSON APIs
64
+ - Plain text
65
+ - Raw content
66
+
67
+ Security:
68
+ - Configurable size limits
69
+ - Timeout protection
70
+ - No file:// URLs
71
+ """
72
+
73
+ DEFAULT_TIMEOUT = 30
74
+ MAX_SIZE = 1024 * 1024 # 1MB default limit
75
+ USER_AGENT = "SuperQode/1.0 (AI Coding Assistant)"
76
+
77
+ @property
78
+ def name(self) -> str:
79
+ return "fetch"
80
+
81
+ @property
82
+ def description(self) -> str:
83
+ return "Fetch content from a URL. Supports HTML (extracts text), JSON, and plain text."
84
+
85
+ @property
86
+ def parameters(self) -> Dict[str, Any]:
87
+ return {
88
+ "type": "object",
89
+ "properties": {
90
+ "url": {"type": "string", "description": "URL to fetch (http or https)"},
91
+ "format": {
92
+ "type": "string",
93
+ "enum": ["auto", "text", "json", "html", "raw"],
94
+ "description": "Response format: auto (detect), text (extract from HTML), json, html (raw HTML), raw (bytes as text)",
95
+ },
96
+ "timeout": {"type": "integer", "description": "Timeout in seconds (default: 30)"},
97
+ "headers": {"type": "object", "description": "Additional HTTP headers to send"},
98
+ "max_size": {
99
+ "type": "integer",
100
+ "description": "Maximum response size in bytes (default: 1MB)",
101
+ },
102
+ },
103
+ "required": ["url"],
104
+ }
105
+
106
+ async def execute(self, args: Dict[str, Any], ctx: ToolContext) -> ToolResult:
107
+ url = args.get("url", "")
108
+ format_type = args.get("format", "auto")
109
+ timeout = args.get("timeout", self.DEFAULT_TIMEOUT)
110
+ headers = args.get("headers", {})
111
+ max_size = args.get("max_size", self.MAX_SIZE)
112
+
113
+ # Validate URL
114
+ if not url:
115
+ return ToolResult(success=False, output="", error="URL is required")
116
+
117
+ if not url.startswith(("http://", "https://")):
118
+ return ToolResult(
119
+ success=False, output="", error="Only http:// and https:// URLs are supported"
120
+ )
121
+
122
+ try:
123
+ # Run fetch in executor to not block
124
+ loop = asyncio.get_event_loop()
125
+ result = await asyncio.wait_for(
126
+ loop.run_in_executor(
127
+ None, lambda: self._sync_fetch(url, headers, timeout, max_size)
128
+ ),
129
+ timeout=timeout + 5, # Extra buffer for executor
130
+ )
131
+
132
+ if result.get("error"):
133
+ return ToolResult(success=False, output="", error=result["error"])
134
+
135
+ content = result["content"]
136
+ content_type = result.get("content_type", "")
137
+
138
+ # Process based on format
139
+ output = self._process_content(content, content_type, format_type)
140
+
141
+ return ToolResult(
142
+ success=True,
143
+ output=output,
144
+ metadata={
145
+ "url": url,
146
+ "content_type": content_type,
147
+ "size": len(content),
148
+ "format": format_type,
149
+ },
150
+ )
151
+
152
+ except asyncio.TimeoutError:
153
+ return ToolResult(
154
+ success=False, output="", error=f"Request timed out after {timeout} seconds"
155
+ )
156
+ except Exception as e:
157
+ return ToolResult(success=False, output="", error=f"Fetch error: {str(e)}")
158
+
159
+ def _sync_fetch(
160
+ self, url: str, headers: Dict[str, str], timeout: int, max_size: int
161
+ ) -> Dict[str, Any]:
162
+ """Synchronous fetch implementation."""
163
+ try:
164
+ # Build request
165
+ req = urllib.request.Request(url)
166
+ req.add_header("User-Agent", self.USER_AGENT)
167
+
168
+ for key, value in headers.items():
169
+ req.add_header(key, value)
170
+
171
+ # Create SSL context
172
+ ctx = ssl.create_default_context()
173
+
174
+ with urllib.request.urlopen(req, timeout=timeout, context=ctx) as response:
175
+ content_type = response.headers.get("Content-Type", "")
176
+
177
+ # Read with size limit
178
+ content = response.read(max_size)
179
+
180
+ # Check if truncated
181
+ extra = response.read(1)
182
+ if extra:
183
+ content += b"\n\n[Content truncated at " + str(max_size).encode() + b" bytes]"
184
+
185
+ # Decode
186
+ charset = self._get_charset(content_type)
187
+ try:
188
+ text = content.decode(charset, errors="replace")
189
+ except (UnicodeDecodeError, LookupError):
190
+ text = content.decode("utf-8", errors="replace")
191
+
192
+ return {"content": text, "content_type": content_type}
193
+
194
+ except urllib.error.HTTPError as e:
195
+ return {"error": f"HTTP {e.code}: {e.reason}"}
196
+ except urllib.error.URLError as e:
197
+ return {"error": f"URL Error: {str(e.reason)}"}
198
+ except Exception as e:
199
+ return {"error": str(e)}
200
+
201
+ def _get_charset(self, content_type: str) -> str:
202
+ """Extract charset from Content-Type header."""
203
+ if not content_type:
204
+ return "utf-8"
205
+
206
+ # Look for charset=
207
+ match = re.search(r"charset=([^\s;]+)", content_type, re.I)
208
+ if match:
209
+ return match.group(1).strip("\"'")
210
+
211
+ return "utf-8"
212
+
213
+ def _process_content(self, content: str, content_type: str, format_type: str) -> str:
214
+ """Process content based on format type."""
215
+ # Auto-detect format
216
+ if format_type == "auto":
217
+ if "application/json" in content_type:
218
+ format_type = "json"
219
+ elif "text/html" in content_type:
220
+ format_type = "text" # Extract text from HTML
221
+ else:
222
+ format_type = "raw"
223
+
224
+ if format_type == "json":
225
+ try:
226
+ data = json.loads(content)
227
+ return json.dumps(data, indent=2)
228
+ except json.JSONDecodeError:
229
+ return content
230
+
231
+ elif format_type == "text":
232
+ # Extract text from HTML
233
+ try:
234
+ parser = HTMLTextExtractor()
235
+ parser.feed(content)
236
+ text = parser.get_text()
237
+ return text if text else content
238
+ except Exception:
239
+ return content
240
+
241
+ elif format_type == "html":
242
+ return content
243
+
244
+ else: # raw
245
+ return content
246
+
247
+
248
+ class DownloadTool(Tool):
249
+ """
250
+ Download a file from a URL.
251
+
252
+ Saves the file to the specified path.
253
+ """
254
+
255
+ DEFAULT_TIMEOUT = 60
256
+ MAX_SIZE = 50 * 1024 * 1024 # 50MB limit
257
+ USER_AGENT = "SuperQode/1.0 (AI Coding Assistant)"
258
+
259
+ @property
260
+ def name(self) -> str:
261
+ return "download"
262
+
263
+ @property
264
+ def description(self) -> str:
265
+ return "Download a file from a URL and save it to a path."
266
+
267
+ @property
268
+ def parameters(self) -> Dict[str, Any]:
269
+ return {
270
+ "type": "object",
271
+ "properties": {
272
+ "url": {"type": "string", "description": "URL to download from"},
273
+ "path": {"type": "string", "description": "Path to save the file"},
274
+ "timeout": {"type": "integer", "description": "Timeout in seconds (default: 60)"},
275
+ },
276
+ "required": ["url", "path"],
277
+ }
278
+
279
+ async def execute(self, args: Dict[str, Any], ctx: ToolContext) -> ToolResult:
280
+ url = args.get("url", "")
281
+ path = args.get("path", "")
282
+ timeout = args.get("timeout", self.DEFAULT_TIMEOUT)
283
+
284
+ if not url or not path:
285
+ return ToolResult(success=False, output="", error="Both url and path are required")
286
+
287
+ if not url.startswith(("http://", "https://")):
288
+ return ToolResult(
289
+ success=False, output="", error="Only http:// and https:// URLs are supported"
290
+ )
291
+
292
+ file_path = Path(path)
293
+ if not file_path.is_absolute():
294
+ file_path = ctx.working_directory / file_path
295
+
296
+ try:
297
+ loop = asyncio.get_event_loop()
298
+ result = await asyncio.wait_for(
299
+ loop.run_in_executor(None, lambda: self._sync_download(url, file_path, timeout)),
300
+ timeout=timeout + 5,
301
+ )
302
+
303
+ if result.get("error"):
304
+ return ToolResult(success=False, output="", error=result["error"])
305
+
306
+ return ToolResult(
307
+ success=True,
308
+ output=f"Downloaded {result['size']} bytes to {path}",
309
+ metadata={"url": url, "path": str(file_path), "size": result["size"]},
310
+ )
311
+
312
+ except asyncio.TimeoutError:
313
+ return ToolResult(
314
+ success=False, output="", error=f"Download timed out after {timeout} seconds"
315
+ )
316
+ except Exception as e:
317
+ return ToolResult(success=False, output="", error=f"Download error: {str(e)}")
318
+
319
+ def _sync_download(self, url: str, file_path: Path, timeout: int) -> Dict[str, Any]:
320
+ """Synchronous download implementation."""
321
+ try:
322
+ req = urllib.request.Request(url)
323
+ req.add_header("User-Agent", self.USER_AGENT)
324
+
325
+ ctx = ssl.create_default_context()
326
+
327
+ file_path.parent.mkdir(parents=True, exist_ok=True)
328
+
329
+ with urllib.request.urlopen(req, timeout=timeout, context=ctx) as response:
330
+ with open(file_path, "wb") as f:
331
+ total = 0
332
+ while True:
333
+ chunk = response.read(8192)
334
+ if not chunk:
335
+ break
336
+
337
+ total += len(chunk)
338
+ if total > self.MAX_SIZE:
339
+ return {"error": f"File exceeds maximum size of {self.MAX_SIZE} bytes"}
340
+
341
+ f.write(chunk)
342
+
343
+ return {"size": total}
344
+
345
+ except urllib.error.HTTPError as e:
346
+ return {"error": f"HTTP {e.code}: {e.reason}"}
347
+ except urllib.error.URLError as e:
348
+ return {"error": f"URL Error: {str(e.reason)}"}
349
+ except Exception as e:
350
+ return {"error": str(e)}