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,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
|