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,531 @@
1
+ """HuggingFace Hub API client for model discovery and download.
2
+
3
+ This module provides access to the HuggingFace Hub API for:
4
+ - Searching models by name, task, library
5
+ - Getting model information (size, license, downloads)
6
+ - Listing GGUF files available for a model
7
+ - Downloading models for local use
8
+ """
9
+
10
+ import asyncio
11
+ import json
12
+ import os
13
+ from dataclasses import dataclass, field
14
+ from datetime import datetime
15
+ from pathlib import Path
16
+ from typing import Any, Dict, List, Optional
17
+ from urllib.error import HTTPError, URLError
18
+ from urllib.parse import quote, urlencode
19
+ from urllib.request import Request, urlopen
20
+
21
+
22
+ # HuggingFace Hub API base URL
23
+ HF_API_BASE = "https://huggingface.co/api"
24
+
25
+
26
+ @dataclass
27
+ class HFModel:
28
+ """Represents a model from the HuggingFace Hub.
29
+
30
+ Attributes:
31
+ id: Model ID (e.g., "meta-llama/Llama-3.3-70B-Instruct")
32
+ author: Model author/organization
33
+ name: Model name without author prefix
34
+ downloads: Total download count
35
+ likes: Number of likes
36
+ trending_score: Trending score (if available)
37
+ library: Primary library (transformers, gguf, etc.)
38
+ pipeline_tag: Task type (text-generation, etc.)
39
+ tags: Model tags
40
+ license: Model license
41
+ gated: Whether model requires access approval
42
+ private: Whether model is private
43
+ created_at: Creation timestamp
44
+ updated_at: Last update timestamp
45
+ sha: Latest commit SHA
46
+ siblings: List of files in the repo
47
+ """
48
+
49
+ id: str
50
+ author: str = ""
51
+ name: str = ""
52
+ downloads: int = 0
53
+ likes: int = 0
54
+ trending_score: float = 0.0
55
+ library: str = ""
56
+ pipeline_tag: str = ""
57
+ tags: List[str] = field(default_factory=list)
58
+ license: str = ""
59
+ gated: bool = False
60
+ private: bool = False
61
+ created_at: Optional[datetime] = None
62
+ updated_at: Optional[datetime] = None
63
+ sha: str = ""
64
+ siblings: List[Dict] = field(default_factory=list)
65
+
66
+ @property
67
+ def downloads_display(self) -> str:
68
+ """Human-readable download count."""
69
+ if self.downloads >= 1_000_000:
70
+ return f"{self.downloads / 1_000_000:.1f}M"
71
+ if self.downloads >= 1_000:
72
+ return f"{self.downloads / 1_000:.1f}K"
73
+ return str(self.downloads)
74
+
75
+ @property
76
+ def is_gguf(self) -> bool:
77
+ """Check if model has GGUF files."""
78
+ return "gguf" in self.library.lower() or any("gguf" in t.lower() for t in self.tags)
79
+
80
+ @property
81
+ def is_gated_llama(self) -> bool:
82
+ """Check if this is a gated Llama model."""
83
+ return self.gated and "llama" in self.id.lower()
84
+
85
+
86
+ @dataclass
87
+ class GGUFFile:
88
+ """Represents a GGUF file available for download.
89
+
90
+ Attributes:
91
+ filename: Name of the GGUF file
92
+ size_bytes: File size in bytes
93
+ quantization: Detected quantization (Q4_K_M, Q8_0, etc.)
94
+ url: Download URL
95
+ sha: File SHA hash
96
+ """
97
+
98
+ filename: str
99
+ size_bytes: int = 0
100
+ quantization: str = "unknown"
101
+ url: str = ""
102
+ sha: str = ""
103
+
104
+ @property
105
+ def size_display(self) -> str:
106
+ """Human-readable size."""
107
+ if self.size_bytes == 0:
108
+ return "unknown"
109
+ gb = self.size_bytes / (1024**3)
110
+ if gb >= 1:
111
+ return f"{gb:.1f}GB"
112
+ mb = self.size_bytes / (1024**2)
113
+ return f"{mb:.0f}MB"
114
+
115
+
116
+ class HuggingFaceHub:
117
+ """HuggingFace Hub API client.
118
+
119
+ Provides access to the HF Hub for model discovery and download.
120
+
121
+ Environment:
122
+ HF_TOKEN: HuggingFace token for private/gated models
123
+ HF_HOME: Cache directory (default: ~/.cache/huggingface)
124
+ """
125
+
126
+ def __init__(self, token: Optional[str] = None, cache_dir: Optional[Path] = None):
127
+ """Initialize the HF Hub client.
128
+
129
+ Args:
130
+ token: HF token for authentication. Falls back to HF_TOKEN env var.
131
+ cache_dir: Cache directory. Falls back to HF_HOME or ~/.cache/huggingface.
132
+ """
133
+ self._token = (
134
+ token or os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN")
135
+ )
136
+
137
+ if cache_dir:
138
+ self._cache_dir = cache_dir
139
+ else:
140
+ hf_home = os.environ.get("HF_HOME", os.path.expanduser("~/.cache/huggingface"))
141
+ self._cache_dir = Path(hf_home)
142
+
143
+ @property
144
+ def token(self) -> Optional[str]:
145
+ """Get the HF token."""
146
+ return self._token
147
+
148
+ @property
149
+ def cache_dir(self) -> Path:
150
+ """Get the cache directory."""
151
+ return self._cache_dir
152
+
153
+ @property
154
+ def is_authenticated(self) -> bool:
155
+ """Check if we have authentication."""
156
+ return self._token is not None and len(self._token) > 0
157
+
158
+ def _request(self, endpoint: str, params: Optional[Dict] = None, timeout: float = 30.0) -> Any:
159
+ """Make a request to the HF Hub API.
160
+
161
+ Args:
162
+ endpoint: API endpoint (e.g., "/models")
163
+ params: Query parameters
164
+ timeout: Request timeout
165
+
166
+ Returns:
167
+ JSON response.
168
+ """
169
+ url = f"{HF_API_BASE}{endpoint}"
170
+
171
+ if params:
172
+ url = f"{url}?{urlencode(params)}"
173
+
174
+ headers = {"Accept": "application/json"}
175
+ if self._token:
176
+ headers["Authorization"] = f"Bearer {self._token}"
177
+
178
+ request = Request(url, headers=headers)
179
+
180
+ with urlopen(request, timeout=timeout) as response:
181
+ return json.loads(response.read().decode("utf-8"))
182
+
183
+ async def _async_request(
184
+ self, endpoint: str, params: Optional[Dict] = None, timeout: float = 30.0
185
+ ) -> Any:
186
+ """Async wrapper for _request."""
187
+ loop = asyncio.get_event_loop()
188
+ return await loop.run_in_executor(None, lambda: self._request(endpoint, params, timeout))
189
+
190
+ async def search_models(
191
+ self,
192
+ query: str = "",
193
+ task: str = "text-generation",
194
+ library: Optional[str] = None,
195
+ sort: str = "downloads",
196
+ direction: str = "-1",
197
+ limit: int = 20,
198
+ ) -> List[HFModel]:
199
+ """Search for models on HF Hub.
200
+
201
+ Args:
202
+ query: Search query (model name, author, etc.)
203
+ task: Task/pipeline type (text-generation, text2text-generation)
204
+ library: Filter by library (transformers, gguf, etc.)
205
+ sort: Sort field (downloads, likes, trending_score, created_at)
206
+ direction: Sort direction (-1 for descending, 1 for ascending)
207
+ limit: Maximum results to return
208
+
209
+ Returns:
210
+ List of HFModel objects.
211
+ """
212
+ params = {
213
+ "limit": str(limit),
214
+ "sort": sort,
215
+ "direction": direction,
216
+ "full": "true", # Include all fields
217
+ }
218
+
219
+ if query:
220
+ params["search"] = query
221
+
222
+ if task:
223
+ params["pipeline_tag"] = task
224
+
225
+ if library:
226
+ params["library"] = library
227
+
228
+ try:
229
+ response = await self._async_request("/models", params)
230
+ return [self._parse_model(m) for m in response]
231
+ except Exception:
232
+ return []
233
+
234
+ async def get_trending(self, limit: int = 20) -> List[HFModel]:
235
+ """Get trending text-generation models.
236
+
237
+ Args:
238
+ limit: Maximum results.
239
+
240
+ Returns:
241
+ List of trending HFModel objects.
242
+ """
243
+ return await self.search_models(task="text-generation", sort="trending_score", limit=limit)
244
+
245
+ async def get_popular_coding(self, limit: int = 20) -> List[HFModel]:
246
+ """Get popular coding/code models.
247
+
248
+ Args:
249
+ limit: Maximum results.
250
+
251
+ Returns:
252
+ List of popular coding HFModel objects.
253
+ """
254
+ # Search for code-related models
255
+ models = []
256
+
257
+ # Try different code-related queries
258
+ for query in ["coder", "code", "starcoder"]:
259
+ results = await self.search_models(
260
+ query=query, task="text-generation", sort="downloads", limit=limit
261
+ )
262
+ for m in results:
263
+ if m.id not in [existing.id for existing in models]:
264
+ models.append(m)
265
+
266
+ if len(models) >= limit:
267
+ break
268
+
269
+ return models[:limit]
270
+
271
+ async def get_model_info(self, model_id: str) -> Optional[HFModel]:
272
+ """Get detailed information about a model.
273
+
274
+ Args:
275
+ model_id: Full model ID (e.g., "meta-llama/Llama-3.3-70B-Instruct")
276
+
277
+ Returns:
278
+ HFModel with detailed info, or None if not found.
279
+ """
280
+ try:
281
+ # URL encode the model ID (handles slashes)
282
+ encoded_id = quote(model_id, safe="")
283
+ response = await self._async_request(f"/models/{encoded_id}")
284
+ return self._parse_model(response, full=True)
285
+ except HTTPError as e:
286
+ if e.code == 404:
287
+ return None
288
+ raise
289
+ except Exception:
290
+ return None
291
+
292
+ async def list_gguf_files(self, model_id: str) -> List[GGUFFile]:
293
+ """List GGUF files available for a model.
294
+
295
+ Args:
296
+ model_id: Full model ID.
297
+
298
+ Returns:
299
+ List of GGUFFile objects available for download.
300
+ """
301
+ model = await self.get_model_info(model_id)
302
+ if not model:
303
+ return []
304
+
305
+ gguf_files = []
306
+ for sibling in model.siblings:
307
+ filename = sibling.get("rfilename", "")
308
+ if filename.lower().endswith(".gguf"):
309
+ size = sibling.get("size", 0)
310
+ sha = sibling.get("sha", "")
311
+
312
+ # Detect quantization from filename
313
+ quant = self._detect_quantization(filename)
314
+
315
+ # Build download URL
316
+ url = f"https://huggingface.co/{model_id}/resolve/main/{filename}"
317
+
318
+ gguf_files.append(
319
+ GGUFFile(
320
+ filename=filename,
321
+ size_bytes=size,
322
+ quantization=quant,
323
+ url=url,
324
+ sha=sha,
325
+ )
326
+ )
327
+
328
+ # Sort by quantization quality (higher quality first)
329
+ quant_order = [
330
+ "F32",
331
+ "F16",
332
+ "BF16",
333
+ "Q8_0",
334
+ "Q6_K",
335
+ "Q5_K_M",
336
+ "Q5_K_S",
337
+ "Q4_K_M",
338
+ "Q4_K_S",
339
+ "Q4_0",
340
+ "Q3_K_M",
341
+ "Q3_K_S",
342
+ "Q2_K",
343
+ ]
344
+
345
+ def sort_key(f: GGUFFile) -> int:
346
+ try:
347
+ return quant_order.index(f.quantization)
348
+ except ValueError:
349
+ return 999
350
+
351
+ gguf_files.sort(key=sort_key)
352
+
353
+ return gguf_files
354
+
355
+ async def search_gguf_models(self, query: str = "", limit: int = 20) -> List[HFModel]:
356
+ """Search specifically for GGUF models.
357
+
358
+ Args:
359
+ query: Search query.
360
+ limit: Maximum results.
361
+
362
+ Returns:
363
+ List of HFModel objects with GGUF files.
364
+ """
365
+ return await self.search_models(
366
+ query=query, task="text-generation", library="gguf", limit=limit
367
+ )
368
+
369
+ async def check_access(self, model_id: str) -> Dict[str, Any]:
370
+ """Check if user has access to a gated model.
371
+
372
+ Args:
373
+ model_id: Model ID to check.
374
+
375
+ Returns:
376
+ Dict with access status info.
377
+ """
378
+ if not self.is_authenticated:
379
+ return {
380
+ "has_access": False,
381
+ "reason": "Not authenticated. Set HF_TOKEN environment variable.",
382
+ "gated": True,
383
+ }
384
+
385
+ model = await self.get_model_info(model_id)
386
+ if not model:
387
+ return {
388
+ "has_access": False,
389
+ "reason": "Model not found",
390
+ "gated": False,
391
+ }
392
+
393
+ if not model.gated:
394
+ return {
395
+ "has_access": True,
396
+ "reason": "Model is not gated",
397
+ "gated": False,
398
+ }
399
+
400
+ # Try to access a file to check permissions
401
+ try:
402
+ # Try to get repo info with credentials
403
+ encoded_id = quote(model_id, safe="")
404
+ await self._async_request(f"/models/{encoded_id}/tree/main")
405
+ return {
406
+ "has_access": True,
407
+ "reason": "Access granted",
408
+ "gated": True,
409
+ }
410
+ except HTTPError as e:
411
+ if e.code == 403:
412
+ return {
413
+ "has_access": False,
414
+ "reason": "Access denied. Request access at https://huggingface.co/" + model_id,
415
+ "gated": True,
416
+ }
417
+ raise
418
+
419
+ def _parse_model(self, data: Dict[str, Any], full: bool = False) -> HFModel:
420
+ """Parse model data from API response."""
421
+ model_id = data.get("id", data.get("modelId", ""))
422
+
423
+ # Split author and name
424
+ author = ""
425
+ name = model_id
426
+ if "/" in model_id:
427
+ author, name = model_id.split("/", 1)
428
+
429
+ # Parse timestamps
430
+ created_at = None
431
+ updated_at = None
432
+ if "createdAt" in data:
433
+ try:
434
+ created_at = datetime.fromisoformat(data["createdAt"].replace("Z", "+00:00"))
435
+ except Exception:
436
+ pass
437
+ if "lastModified" in data:
438
+ try:
439
+ updated_at = datetime.fromisoformat(data["lastModified"].replace("Z", "+00:00"))
440
+ except Exception:
441
+ pass
442
+
443
+ # Get library
444
+ library = ""
445
+ if "library_name" in data:
446
+ library = data["library_name"]
447
+ elif "tags" in data:
448
+ # Check tags for library info
449
+ for tag in data.get("tags", []):
450
+ if tag in ("transformers", "gguf", "pytorch", "tensorflow", "jax"):
451
+ library = tag
452
+ break
453
+
454
+ return HFModel(
455
+ id=model_id,
456
+ author=author,
457
+ name=name,
458
+ downloads=data.get("downloads", 0),
459
+ likes=data.get("likes", 0),
460
+ trending_score=data.get("trendingScore", 0.0),
461
+ library=library,
462
+ pipeline_tag=data.get("pipeline_tag", ""),
463
+ tags=data.get("tags", []),
464
+ license=self._extract_license(data),
465
+ gated=data.get("gated", False) or data.get("gated", "") == "auto",
466
+ private=data.get("private", False),
467
+ created_at=created_at,
468
+ updated_at=updated_at,
469
+ sha=data.get("sha", ""),
470
+ siblings=data.get("siblings", []) if full else [],
471
+ )
472
+
473
+ def _extract_license(self, data: Dict[str, Any]) -> str:
474
+ """Extract license from model data."""
475
+ # Check card_data first
476
+ card_data = data.get("cardData", {})
477
+ if "license" in card_data:
478
+ return card_data["license"]
479
+
480
+ # Check tags for license
481
+ for tag in data.get("tags", []):
482
+ if tag.startswith("license:"):
483
+ return tag.split(":", 1)[1]
484
+
485
+ return ""
486
+
487
+ def _detect_quantization(self, filename: str) -> str:
488
+ """Detect quantization from GGUF filename."""
489
+ filename_upper = filename.upper()
490
+
491
+ # Common quantization patterns
492
+ quants = [
493
+ "Q8_0",
494
+ "Q6_K",
495
+ "Q5_K_M",
496
+ "Q5_K_S",
497
+ "Q4_K_M",
498
+ "Q4_K_S",
499
+ "Q4_0",
500
+ "Q3_K_M",
501
+ "Q3_K_S",
502
+ "Q2_K",
503
+ "IQ4_XS",
504
+ "IQ3_XS",
505
+ "IQ2_XS",
506
+ "F32",
507
+ "F16",
508
+ "BF16",
509
+ ]
510
+
511
+ for quant in quants:
512
+ if quant in filename_upper or quant.replace("_", "-") in filename_upper:
513
+ return quant
514
+
515
+ return "unknown"
516
+
517
+
518
+ # Singleton instance
519
+ _hub_instance: Optional[HuggingFaceHub] = None
520
+
521
+
522
+ def get_hf_hub() -> HuggingFaceHub:
523
+ """Get the global HuggingFace Hub client instance.
524
+
525
+ Returns:
526
+ HuggingFaceHub client instance.
527
+ """
528
+ global _hub_instance
529
+ if _hub_instance is None:
530
+ _hub_instance = HuggingFaceHub()
531
+ return _hub_instance