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,442 @@
1
+ """HuggingFace Inference Endpoints client for dedicated deployments.
2
+
3
+ This module provides access to HuggingFace Inference Endpoints,
4
+ allowing users to connect to their dedicated model deployments.
5
+ """
6
+
7
+ import asyncio
8
+ import json
9
+ import os
10
+ from dataclasses import dataclass, field
11
+ from datetime import datetime
12
+ from enum import Enum
13
+ from typing import Any, Dict, List, Optional
14
+ from urllib.error import HTTPError, URLError
15
+ from urllib.request import Request, urlopen
16
+
17
+
18
+ # HuggingFace Inference Endpoints API
19
+ HF_ENDPOINTS_API = "https://api.endpoints.huggingface.cloud/v2"
20
+
21
+
22
+ class EndpointState(Enum):
23
+ """Possible states for an Inference Endpoint."""
24
+
25
+ PENDING = "pending"
26
+ INITIALIZING = "initializing"
27
+ UPDATING = "updating"
28
+ RUNNING = "running"
29
+ PAUSED = "paused"
30
+ FAILED = "failed"
31
+ SCALED_TO_ZERO = "scaledToZero"
32
+
33
+
34
+ class EndpointType(Enum):
35
+ """Types of Inference Endpoints."""
36
+
37
+ PROTECTED = "protected" # Requires HF token
38
+ PUBLIC = "public" # Anyone can access
39
+ PRIVATE = "private" # Private VPC
40
+
41
+
42
+ @dataclass
43
+ class InferenceEndpoint:
44
+ """Represents an HF Inference Endpoint.
45
+
46
+ Attributes:
47
+ name: Endpoint name
48
+ model_id: Deployed model ID
49
+ url: Endpoint URL for inference
50
+ state: Current endpoint state
51
+ type: Endpoint type (protected, public, private)
52
+ instance_type: Hardware instance type
53
+ instance_size: Instance size configuration
54
+ region: Deployment region
55
+ created_at: Creation timestamp
56
+ updated_at: Last update timestamp
57
+ revision: Model revision/commit
58
+ framework: ML framework (pytorch, etc.)
59
+ task: Task type (text-generation, etc.)
60
+ scaling: Scaling configuration
61
+ """
62
+
63
+ name: str
64
+ model_id: str
65
+ url: str = ""
66
+ state: EndpointState = EndpointState.PENDING
67
+ type: EndpointType = EndpointType.PROTECTED
68
+ instance_type: str = ""
69
+ instance_size: str = ""
70
+ region: str = ""
71
+ created_at: Optional[datetime] = None
72
+ updated_at: Optional[datetime] = None
73
+ revision: str = ""
74
+ framework: str = ""
75
+ task: str = ""
76
+ scaling: Dict[str, Any] = field(default_factory=dict)
77
+
78
+ @property
79
+ def is_running(self) -> bool:
80
+ """Check if endpoint is running and ready."""
81
+ return self.state == EndpointState.RUNNING
82
+
83
+ @property
84
+ def is_paused(self) -> bool:
85
+ """Check if endpoint is paused."""
86
+ return self.state in (EndpointState.PAUSED, EndpointState.SCALED_TO_ZERO)
87
+
88
+
89
+ @dataclass
90
+ class EndpointResponse:
91
+ """Response from an Inference Endpoint.
92
+
93
+ Attributes:
94
+ content: Generated text
95
+ model: Model ID
96
+ usage: Token usage
97
+ error: Error message if failed
98
+ """
99
+
100
+ content: str = ""
101
+ model: str = ""
102
+ usage: Dict[str, int] = field(default_factory=dict)
103
+ error: str = ""
104
+
105
+
106
+ class HFEndpointsClient:
107
+ """HuggingFace Inference Endpoints client.
108
+
109
+ Provides access to dedicated Inference Endpoints for production deployments.
110
+
111
+ Environment:
112
+ HF_TOKEN: Required for accessing endpoints API and protected endpoints
113
+ """
114
+
115
+ def __init__(self, token: Optional[str] = None, namespace: Optional[str] = None):
116
+ """Initialize the Endpoints client.
117
+
118
+ Args:
119
+ token: HF token. Falls back to HF_TOKEN env var.
120
+ namespace: HF organization/username namespace.
121
+ """
122
+ self._token = (
123
+ token or os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN")
124
+ )
125
+ self._namespace = namespace
126
+
127
+ @property
128
+ def is_authenticated(self) -> bool:
129
+ """Check if we have authentication."""
130
+ return self._token is not None and len(self._token) > 0
131
+
132
+ def _request(
133
+ self, method: str, endpoint: str, data: Optional[Dict] = None, timeout: float = 30.0
134
+ ) -> Any:
135
+ """Make a request to the Endpoints API.
136
+
137
+ Args:
138
+ method: HTTP method.
139
+ endpoint: API endpoint.
140
+ data: Request body.
141
+ timeout: Request timeout.
142
+
143
+ Returns:
144
+ JSON response.
145
+ """
146
+ url = f"{HF_ENDPOINTS_API}{endpoint}"
147
+
148
+ headers = {
149
+ "Accept": "application/json",
150
+ }
151
+
152
+ if self._token:
153
+ headers["Authorization"] = f"Bearer {self._token}"
154
+
155
+ body = None
156
+ if data:
157
+ headers["Content-Type"] = "application/json"
158
+ body = json.dumps(data).encode("utf-8")
159
+
160
+ request = Request(url, data=body, headers=headers, method=method)
161
+
162
+ with urlopen(request, timeout=timeout) as response:
163
+ return json.loads(response.read().decode("utf-8"))
164
+
165
+ async def _async_request(
166
+ self, method: str, endpoint: str, data: Optional[Dict] = None, timeout: float = 30.0
167
+ ) -> Any:
168
+ """Async wrapper for _request."""
169
+ loop = asyncio.get_event_loop()
170
+ return await loop.run_in_executor(
171
+ None, lambda: self._request(method, endpoint, data, timeout)
172
+ )
173
+
174
+ async def list_endpoints(self, namespace: Optional[str] = None) -> List[InferenceEndpoint]:
175
+ """List all Inference Endpoints.
176
+
177
+ Args:
178
+ namespace: Filter by namespace (organization/username).
179
+
180
+ Returns:
181
+ List of InferenceEndpoint objects.
182
+ """
183
+ if not self.is_authenticated:
184
+ return []
185
+
186
+ try:
187
+ ns = namespace or self._namespace
188
+ endpoint_url = "/endpoint"
189
+ if ns:
190
+ endpoint_url = f"/endpoint?namespace={ns}"
191
+
192
+ response = await self._async_request("GET", endpoint_url)
193
+
194
+ endpoints = []
195
+ items = response.get("items", response) if isinstance(response, dict) else response
196
+
197
+ for item in items:
198
+ endpoints.append(self._parse_endpoint(item))
199
+
200
+ return endpoints
201
+
202
+ except HTTPError as e:
203
+ if e.code == 401:
204
+ return [] # Not authenticated
205
+ raise
206
+ except Exception:
207
+ return []
208
+
209
+ async def get_endpoint(
210
+ self, name: str, namespace: Optional[str] = None
211
+ ) -> Optional[InferenceEndpoint]:
212
+ """Get a specific Inference Endpoint.
213
+
214
+ Args:
215
+ name: Endpoint name.
216
+ namespace: Namespace (organization/username).
217
+
218
+ Returns:
219
+ InferenceEndpoint or None if not found.
220
+ """
221
+ if not self.is_authenticated:
222
+ return None
223
+
224
+ ns = namespace or self._namespace
225
+ if not ns:
226
+ # Try to find from list
227
+ endpoints = await self.list_endpoints()
228
+ for ep in endpoints:
229
+ if ep.name == name:
230
+ return ep
231
+ return None
232
+
233
+ try:
234
+ response = await self._async_request("GET", f"/endpoint/{ns}/{name}")
235
+ return self._parse_endpoint(response)
236
+ except HTTPError as e:
237
+ if e.code == 404:
238
+ return None
239
+ raise
240
+ except Exception:
241
+ return None
242
+
243
+ async def get_endpoint_status(
244
+ self, name: str, namespace: Optional[str] = None
245
+ ) -> Dict[str, Any]:
246
+ """Get the status of an Inference Endpoint.
247
+
248
+ Args:
249
+ name: Endpoint name.
250
+ namespace: Namespace.
251
+
252
+ Returns:
253
+ Status dictionary.
254
+ """
255
+ endpoint = await self.get_endpoint(name, namespace)
256
+
257
+ if not endpoint:
258
+ return {
259
+ "available": False,
260
+ "error": "Endpoint not found",
261
+ }
262
+
263
+ return {
264
+ "available": endpoint.is_running,
265
+ "state": endpoint.state.value,
266
+ "url": endpoint.url,
267
+ "model": endpoint.model_id,
268
+ "paused": endpoint.is_paused,
269
+ }
270
+
271
+ async def chat(
272
+ self,
273
+ endpoint_url: str,
274
+ messages: List[Dict[str, str]],
275
+ max_tokens: int = 2048,
276
+ temperature: float = 0.7,
277
+ tools: Optional[List[Dict]] = None,
278
+ ) -> EndpointResponse:
279
+ """Send a chat completion request to an endpoint.
280
+
281
+ Args:
282
+ endpoint_url: Full endpoint URL.
283
+ messages: Chat messages.
284
+ max_tokens: Maximum tokens to generate.
285
+ temperature: Sampling temperature.
286
+ tools: Tool definitions for function calling.
287
+
288
+ Returns:
289
+ EndpointResponse with generated content.
290
+ """
291
+ payload: Dict[str, Any] = {
292
+ "messages": messages,
293
+ "max_tokens": max_tokens,
294
+ "temperature": temperature,
295
+ "stream": False,
296
+ }
297
+
298
+ if tools:
299
+ payload["tools"] = tools
300
+
301
+ try:
302
+ # Endpoints use OpenAI-compatible format
303
+ chat_url = endpoint_url.rstrip("/") + "/v1/chat/completions"
304
+
305
+ headers = {
306
+ "Content-Type": "application/json",
307
+ "Accept": "application/json",
308
+ }
309
+
310
+ if self._token:
311
+ headers["Authorization"] = f"Bearer {self._token}"
312
+
313
+ body = json.dumps(payload).encode("utf-8")
314
+ request = Request(chat_url, data=body, headers=headers, method="POST")
315
+
316
+ loop = asyncio.get_event_loop()
317
+
318
+ def do_request():
319
+ with urlopen(request, timeout=120.0) as response:
320
+ return json.loads(response.read().decode("utf-8"))
321
+
322
+ response = await loop.run_in_executor(None, do_request)
323
+
324
+ return self._parse_chat_response(response)
325
+
326
+ except HTTPError as e:
327
+ error_body = ""
328
+ try:
329
+ error_body = e.read().decode("utf-8")
330
+ except Exception:
331
+ pass
332
+
333
+ return EndpointResponse(error=f"HTTP {e.code}: {error_body or e.reason}")
334
+
335
+ except Exception as e:
336
+ return EndpointResponse(error=str(e))
337
+
338
+ def _parse_endpoint(self, data: Dict[str, Any]) -> InferenceEndpoint:
339
+ """Parse endpoint data from API response."""
340
+ # Parse state
341
+ state_str = data.get("status", {}).get("state", "pending")
342
+ try:
343
+ state = EndpointState(state_str.lower().replace("-", "_"))
344
+ except ValueError:
345
+ state = EndpointState.PENDING
346
+
347
+ # Parse type
348
+ type_str = data.get("type", "protected")
349
+ try:
350
+ endpoint_type = EndpointType(type_str.lower())
351
+ except ValueError:
352
+ endpoint_type = EndpointType.PROTECTED
353
+
354
+ # Parse timestamps
355
+ created_at = None
356
+ updated_at = None
357
+ if "createdAt" in data:
358
+ try:
359
+ created_at = datetime.fromisoformat(data["createdAt"].replace("Z", "+00:00"))
360
+ except Exception:
361
+ pass
362
+ if "updatedAt" in data:
363
+ try:
364
+ updated_at = datetime.fromisoformat(data["updatedAt"].replace("Z", "+00:00"))
365
+ except Exception:
366
+ pass
367
+
368
+ # Get model info
369
+ model_info = data.get("model", {})
370
+ model_id = model_info.get("repository", "")
371
+ revision = model_info.get("revision", "")
372
+ framework = model_info.get("framework", "")
373
+ task = model_info.get("task", "")
374
+
375
+ # Get compute info
376
+ compute = data.get("compute", {})
377
+ instance_type = compute.get("instanceType", "")
378
+ instance_size = compute.get("instanceSize", "")
379
+
380
+ # Get URL
381
+ status = data.get("status", {})
382
+ url = status.get("url", "")
383
+
384
+ return InferenceEndpoint(
385
+ name=data.get("name", ""),
386
+ model_id=model_id,
387
+ url=url,
388
+ state=state,
389
+ type=endpoint_type,
390
+ instance_type=instance_type,
391
+ instance_size=instance_size,
392
+ region=data.get("provider", {}).get("region", ""),
393
+ created_at=created_at,
394
+ updated_at=updated_at,
395
+ revision=revision,
396
+ framework=framework,
397
+ task=task,
398
+ scaling=compute.get("scaling", {}),
399
+ )
400
+
401
+ def _parse_chat_response(self, response: Dict[str, Any]) -> EndpointResponse:
402
+ """Parse a chat completion response."""
403
+ choices = response.get("choices", [])
404
+
405
+ if not choices:
406
+ if "error" in response:
407
+ return EndpointResponse(
408
+ error=response.get("error", {}).get("message", str(response["error"]))
409
+ )
410
+ return EndpointResponse(error="No response choices")
411
+
412
+ choice = choices[0]
413
+ message = choice.get("message", {})
414
+ content = message.get("content", "")
415
+
416
+ usage = response.get("usage", {})
417
+
418
+ return EndpointResponse(
419
+ content=content,
420
+ model=response.get("model", ""),
421
+ usage={
422
+ "prompt_tokens": usage.get("prompt_tokens", 0),
423
+ "completion_tokens": usage.get("completion_tokens", 0),
424
+ "total_tokens": usage.get("total_tokens", 0),
425
+ },
426
+ )
427
+
428
+
429
+ # Singleton instance
430
+ _endpoints_client: Optional[HFEndpointsClient] = None
431
+
432
+
433
+ def get_hf_endpoints_client() -> HFEndpointsClient:
434
+ """Get the global HF Endpoints client instance.
435
+
436
+ Returns:
437
+ HFEndpointsClient instance.
438
+ """
439
+ global _endpoints_client
440
+ if _endpoints_client is None:
441
+ _endpoints_client = HFEndpointsClient()
442
+ return _endpoints_client