hypercli-sdk 0.8.1__tar.gz → 0.8.3__tar.gz

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 (25) hide show
  1. {hypercli_sdk-0.8.1 → hypercli_sdk-0.8.3}/PKG-INFO +1 -1
  2. {hypercli_sdk-0.8.1 → hypercli_sdk-0.8.3}/hypercli/__init__.py +6 -1
  3. hypercli_sdk-0.8.3/hypercli/x402.py +183 -0
  4. {hypercli_sdk-0.8.1 → hypercli_sdk-0.8.3}/pyproject.toml +1 -1
  5. {hypercli_sdk-0.8.1 → hypercli_sdk-0.8.3}/.gitignore +0 -0
  6. {hypercli_sdk-0.8.1 → hypercli_sdk-0.8.3}/README.md +0 -0
  7. {hypercli_sdk-0.8.1 → hypercli_sdk-0.8.3}/hypercli/billing.py +0 -0
  8. {hypercli_sdk-0.8.1 → hypercli_sdk-0.8.3}/hypercli/claw.py +0 -0
  9. {hypercli_sdk-0.8.1 → hypercli_sdk-0.8.3}/hypercli/client.py +0 -0
  10. {hypercli_sdk-0.8.1 → hypercli_sdk-0.8.3}/hypercli/config.py +0 -0
  11. {hypercli_sdk-0.8.1 → hypercli_sdk-0.8.3}/hypercli/files.py +0 -0
  12. {hypercli_sdk-0.8.1 → hypercli_sdk-0.8.3}/hypercli/http.py +0 -0
  13. {hypercli_sdk-0.8.1 → hypercli_sdk-0.8.3}/hypercli/instances.py +0 -0
  14. {hypercli_sdk-0.8.1 → hypercli_sdk-0.8.3}/hypercli/job/__init__.py +0 -0
  15. {hypercli_sdk-0.8.1 → hypercli_sdk-0.8.3}/hypercli/job/base.py +0 -0
  16. {hypercli_sdk-0.8.1 → hypercli_sdk-0.8.3}/hypercli/job/comfyui.py +0 -0
  17. {hypercli_sdk-0.8.1 → hypercli_sdk-0.8.3}/hypercli/job/gradio.py +0 -0
  18. {hypercli_sdk-0.8.1 → hypercli_sdk-0.8.3}/hypercli/jobs.py +0 -0
  19. {hypercli_sdk-0.8.1 → hypercli_sdk-0.8.3}/hypercli/keys.py +0 -0
  20. {hypercli_sdk-0.8.1 → hypercli_sdk-0.8.3}/hypercli/logs.py +0 -0
  21. {hypercli_sdk-0.8.1 → hypercli_sdk-0.8.3}/hypercli/renders.py +0 -0
  22. {hypercli_sdk-0.8.1 → hypercli_sdk-0.8.3}/hypercli/user.py +0 -0
  23. {hypercli_sdk-0.8.1 → hypercli_sdk-0.8.3}/tests/test_apply_params.py +0 -0
  24. {hypercli_sdk-0.8.1 → hypercli_sdk-0.8.3}/tests/test_claw.py +0 -0
  25. {hypercli_sdk-0.8.1 → hypercli_sdk-0.8.3}/tests/test_graph_to_api.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hypercli-sdk
3
- Version: 0.8.1
3
+ Version: 0.8.3
4
4
  Summary: Python SDK for HyperCLI - GPU orchestration and LLM API
5
5
  Project-URL: Homepage, https://hypercli.com
6
6
  Project-URL: Documentation, https://docs.hypercli.com
@@ -5,12 +5,13 @@ from .http import APIError, AsyncHTTPClient
5
5
  from .instances import GPUType, GPUConfig, Region, GPUPricing, PricingTier
6
6
  from .jobs import Job, JobMetrics, GPUMetrics, find_job, find_by_id, find_by_hostname, find_by_ip
7
7
  from .renders import Render, RenderStatus
8
+ from .x402 import X402Client, X402JobLaunch, X402RenderCreate
8
9
  from .files import File, AsyncFiles
9
10
  from .job import BaseJob, ComfyUIJob, GradioJob, apply_params, apply_graph_modes, find_node, find_nodes, load_template, graph_to_api, expand_subgraphs, DEFAULT_OBJECT_INFO
10
11
  from .logs import LogStream, stream_logs, fetch_logs
11
12
  from .claw import Claw, ClawKey, ClawPlan, ClawModel
12
13
 
13
- __version__ = "0.5.0"
14
+ __version__ = "0.8.3"
14
15
  __all__ = [
15
16
  "HyperCLI",
16
17
  "configure",
@@ -31,6 +32,10 @@ __all__ = [
31
32
  # Renders API
32
33
  "Render",
33
34
  "RenderStatus",
35
+ # x402 API
36
+ "X402Client",
37
+ "X402JobLaunch",
38
+ "X402RenderCreate",
34
39
  # Files API
35
40
  "File",
36
41
  "AsyncFiles",
@@ -0,0 +1,183 @@
1
+ """x402 payment helpers for pay-per-use job and render launches."""
2
+ from __future__ import annotations
3
+
4
+ import base64
5
+ from dataclasses import dataclass
6
+ from typing import Any
7
+
8
+ import httpx
9
+
10
+ from .config import get_api_url
11
+ from .http import APIError
12
+ from .jobs import Job
13
+ from .renders import Render
14
+
15
+
16
+ @dataclass
17
+ class X402JobLaunch:
18
+ """Response payload for x402 job launch."""
19
+
20
+ job: Job
21
+ access_key: str
22
+ status_url: str
23
+ logs_url: str
24
+ cancel_url: str
25
+
26
+ @classmethod
27
+ def from_dict(cls, data: dict[str, Any]) -> "X402JobLaunch":
28
+ return cls(
29
+ job=Job.from_dict(data.get("job", {})),
30
+ access_key=data.get("access_key", ""),
31
+ status_url=data.get("status_url", ""),
32
+ logs_url=data.get("logs_url", ""),
33
+ cancel_url=data.get("cancel_url", ""),
34
+ )
35
+
36
+
37
+ @dataclass
38
+ class X402RenderCreate:
39
+ """Response payload for x402 render creation."""
40
+
41
+ render: Render
42
+ access_key: str
43
+ status_url: str
44
+ cancel_url: str
45
+
46
+ @classmethod
47
+ def from_dict(cls, data: dict[str, Any]) -> "X402RenderCreate":
48
+ return cls(
49
+ render=Render.from_dict(data.get("render", {})),
50
+ access_key=data.get("access_key", ""),
51
+ status_url=data.get("status_url", ""),
52
+ cancel_url=data.get("cancel_url", ""),
53
+ )
54
+
55
+
56
+ def _require_x402_deps():
57
+ try:
58
+ from x402 import x402ClientSync
59
+ from x402.http import x402HTTPClientSync
60
+ from x402.mechanisms.evm import EthAccountSigner
61
+ from x402.mechanisms.evm.exact.register import register_exact_evm_client
62
+ return x402ClientSync, x402HTTPClientSync, EthAccountSigner, register_exact_evm_client
63
+ except ImportError as exc:
64
+ raise RuntimeError(
65
+ "x402 dependencies missing. Install with: pip install 'x402[httpx,evm]' eth-account"
66
+ ) from exc
67
+
68
+
69
+ def _error_detail(response: httpx.Response) -> str:
70
+ try:
71
+ data = response.json()
72
+ if isinstance(data, dict):
73
+ return str(data.get("detail") or data.get("message") or data)
74
+ return str(data)
75
+ except Exception:
76
+ return response.text
77
+
78
+
79
+ def _x402_post(
80
+ base_url: str,
81
+ path: str,
82
+ payload: dict[str, Any],
83
+ account: Any,
84
+ timeout: float,
85
+ ) -> dict[str, Any]:
86
+ x402ClientSync, x402HTTPClientSync, EthAccountSigner, register_exact_evm_client = _require_x402_deps()
87
+
88
+ signer = EthAccountSigner(account)
89
+ x402_client = x402ClientSync()
90
+ register_exact_evm_client(x402_client, signer)
91
+ http_client = x402HTTPClientSync(x402_client)
92
+
93
+ endpoint = f"{base_url.rstrip('/')}{path}"
94
+ headers = {"Content-Type": "application/json"}
95
+
96
+ with httpx.Client(timeout=timeout) as client:
97
+ response = client.post(endpoint, headers=headers, json=payload)
98
+
99
+ if response.status_code == 402:
100
+ payment_headers, _ = http_client.handle_402_response(dict(response.headers), response.content)
101
+ retry_headers = {**headers, **payment_headers}
102
+ retry_headers["Access-Control-Expose-Headers"] = "PAYMENT-RESPONSE,X-PAYMENT-RESPONSE"
103
+ response = client.post(endpoint, headers=retry_headers, json=payload)
104
+
105
+ if response.status_code >= 400:
106
+ raise APIError(response.status_code, _error_detail(response))
107
+
108
+ data = response.json()
109
+ if not isinstance(data, dict):
110
+ raise APIError(response.status_code, "Malformed API response")
111
+ return data
112
+
113
+
114
+ class X402Client:
115
+ """x402 pay-per-use client for launching jobs and renders without a full API account."""
116
+
117
+ def __init__(self, api_url: str | None = None, timeout: float = 30.0):
118
+ self.api_url = (api_url or get_api_url()).rstrip("/")
119
+ self.timeout = timeout
120
+
121
+ def create_job(
122
+ self,
123
+ *,
124
+ amount: float,
125
+ account: Any,
126
+ image: str,
127
+ command: str | None = None,
128
+ gpu_type: str = "l40s",
129
+ gpu_count: int = 1,
130
+ region: str | None = None,
131
+ interruptible: bool = True,
132
+ env: dict[str, str] | None = None,
133
+ ports: dict[str, int] | None = None,
134
+ auth: bool = False,
135
+ registry_auth: dict[str, str] | None = None,
136
+ ) -> X402JobLaunch:
137
+ if amount <= 0:
138
+ raise ValueError("amount must be greater than 0")
139
+
140
+ job_payload: dict[str, Any] = {
141
+ "docker_image": image,
142
+ "gpu_type": gpu_type,
143
+ "gpu_count": gpu_count,
144
+ "interruptible": interruptible,
145
+ "command": base64.b64encode((command or "").encode()).decode(),
146
+ }
147
+ if region:
148
+ job_payload["region"] = region
149
+ if env:
150
+ job_payload["env_vars"] = env
151
+ if ports:
152
+ job_payload["ports"] = ports
153
+ if auth:
154
+ job_payload["auth"] = auth
155
+ if registry_auth:
156
+ job_payload["registry_auth"] = registry_auth
157
+
158
+ payload = {"amount": amount, "job": job_payload}
159
+ data = _x402_post(self.api_url, "/api/x402/job", payload, account, self.timeout)
160
+ return X402JobLaunch.from_dict(data)
161
+
162
+ def create_render(
163
+ self,
164
+ *,
165
+ amount: float,
166
+ account: Any,
167
+ params: dict[str, Any],
168
+ render_type: str = "comfyui",
169
+ notify_url: str | None = None,
170
+ ) -> X402RenderCreate:
171
+ if amount <= 0:
172
+ raise ValueError("amount must be greater than 0")
173
+
174
+ render_payload: dict[str, Any] = {
175
+ "type": render_type,
176
+ "params": params,
177
+ }
178
+ if notify_url:
179
+ render_payload["notify_url"] = notify_url
180
+
181
+ payload = {"amount": amount, "render": render_payload}
182
+ data = _x402_post(self.api_url, "/api/x402/render", payload, account, self.timeout)
183
+ return X402RenderCreate.from_dict(data)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "hypercli-sdk"
7
- version = "0.8.1"
7
+ version = "0.8.3"
8
8
  description = "Python SDK for HyperCLI - GPU orchestration and LLM API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
File without changes
File without changes