hypercli-sdk 2026.4.18__tar.gz → 2026.4.20__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 (53) hide show
  1. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/PKG-INFO +1 -1
  2. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/hypercli/__init__.py +1 -1
  3. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/hypercli/agent.py +51 -1
  4. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/hypercli/agents.py +15 -0
  5. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/pyproject.toml +1 -1
  6. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/tests/test_agents.py +42 -0
  7. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/tests/test_claw.py +102 -0
  8. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/.gitignore +0 -0
  9. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/README.md +0 -0
  10. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/hypercli/billing.py +0 -0
  11. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/hypercli/client.py +0 -0
  12. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/hypercli/config.py +0 -0
  13. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/hypercli/files.py +0 -0
  14. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/hypercli/gateway.py +0 -0
  15. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/hypercli/http.py +0 -0
  16. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/hypercli/instances.py +0 -0
  17. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/hypercli/job/__init__.py +0 -0
  18. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/hypercli/job/base.py +0 -0
  19. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/hypercli/job/comfyui.py +0 -0
  20. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/hypercli/job/gradio.py +0 -0
  21. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/hypercli/jobs.py +0 -0
  22. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/hypercli/keys.py +0 -0
  23. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/hypercli/logs.py +0 -0
  24. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/hypercli/models.py +0 -0
  25. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/hypercli/openclaw/__init__.py +0 -0
  26. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/hypercli/openclaw/gateway.py +0 -0
  27. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/hypercli/renders.py +0 -0
  28. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/hypercli/shell.py +0 -0
  29. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/hypercli/user.py +0 -0
  30. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/hypercli/voice.py +0 -0
  31. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/hypercli/x402.py +0 -0
  32. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/tests/integration/conftest.py +0 -0
  33. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/tests/integration/test_agents.py +0 -0
  34. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/tests/integration/test_auth.py +0 -0
  35. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/tests/integration/test_billing.py +0 -0
  36. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/tests/integration/test_instances.py +0 -0
  37. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/tests/integration/test_jobs_dryrun.py +0 -0
  38. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/tests/integration/test_keys.py +0 -0
  39. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/tests/integration/test_renders.py +0 -0
  40. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/tests/test_apply_params.py +0 -0
  41. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/tests/test_bootstrap_console_test_key.py +0 -0
  42. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/tests/test_bootstrap_dev_test_keys.py +0 -0
  43. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/tests/test_config.py +0 -0
  44. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/tests/test_exec_shell_dryrun.py +0 -0
  45. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/tests/test_gateway.py +0 -0
  46. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/tests/test_gateway_retry.py +0 -0
  47. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/tests/test_graph_to_api.py +0 -0
  48. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/tests/test_http.py +0 -0
  49. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/tests/test_jobs.py +0 -0
  50. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/tests/test_keys.py +0 -0
  51. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/tests/test_models.py +0 -0
  52. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/tests/test_renders_subscription.py +0 -0
  53. {hypercli_sdk-2026.4.18 → hypercli_sdk-2026.4.20}/tests/test_voice.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hypercli-sdk
3
- Version: 2026.4.18
3
+ Version: 2026.4.20
4
4
  Summary: Python SDK for HyperCLI - GPU orchestration and HyperAgent API
5
5
  Project-URL: Homepage, https://hypercli.com
6
6
  Project-URL: Documentation, https://docs.hypercli.com
@@ -78,7 +78,7 @@ from .gateway import (
78
78
  extract_gateway_chat_tool_calls,
79
79
  normalize_gateway_chat_message,
80
80
  )
81
- __version__ = "2026.4.18"
81
+ __version__ = "2026.4.20"
82
82
  __all__ = [
83
83
  "HyperCLI",
84
84
  "configure",
@@ -7,7 +7,7 @@ Uses the official OpenAI Python client for chat completions.
7
7
  from dataclasses import dataclass
8
8
  from datetime import datetime
9
9
  from typing import Any, Dict, List, Optional, Union
10
- from urllib.parse import urlsplit
10
+ from urllib.parse import quote, urlsplit
11
11
 
12
12
  from .config import get_agents_api_base_url
13
13
  from .http import HTTPClient
@@ -266,6 +266,7 @@ class HyperAgentSubscriptionSummary:
266
266
  active_subscription_count: int
267
267
  active_entitlement_count: int
268
268
  entitlements: HyperAgentEntitlements
269
+ entitlement_items: list[HyperAgentEntitlement]
269
270
  active_subscriptions: list[HyperAgentSubscription]
270
271
  subscriptions: list[HyperAgentSubscription]
271
272
  user: dict[str, Any]
@@ -286,6 +287,7 @@ class HyperAgentSubscriptionSummary:
286
287
  active_subscription_count=int(data.get("active_subscription_count", 0) or 0),
287
288
  active_entitlement_count=int(data.get("active_entitlement_count", data.get("active_subscription_count", 0)) or 0),
288
289
  entitlements=HyperAgentEntitlements.from_dict(data),
290
+ entitlement_items=[HyperAgentEntitlement.from_dict(item) for item in data.get("entitlement_items", [])],
289
291
  active_subscriptions=[HyperAgentSubscription.from_dict(item) for item in data.get("active_subscriptions", [])],
290
292
  subscriptions=[HyperAgentSubscription.from_dict(item) for item in data.get("subscriptions", [])],
291
293
  user=data.get("user") or {},
@@ -909,6 +911,54 @@ class HyperAgent:
909
911
  response.raise_for_status()
910
912
  return response.json()
911
913
 
914
+ def purchase_via_x402(
915
+ self,
916
+ plan_id: str,
917
+ *,
918
+ quantity: int | None = None,
919
+ bundle: dict[str, int] | None = None,
920
+ ) -> HyperAgentX402CheckoutResponse:
921
+ payload: dict[str, Any] = {}
922
+ if quantity is not None:
923
+ payload["quantity"] = int(quantity)
924
+ if bundle is not None:
925
+ payload["bundle"] = {str(k): int(v) for k, v in bundle.items()}
926
+ response = self._http._session.post(
927
+ f"{self._control_base_url}/x402/{quote(str(plan_id), safe='')}",
928
+ headers={"Authorization": f"Bearer {self._api_key}"},
929
+ json=payload,
930
+ )
931
+ response.raise_for_status()
932
+ return HyperAgentX402CheckoutResponse.from_dict(response.json())
933
+
934
+ def purchase_bundle_via_x402(
935
+ self,
936
+ *,
937
+ quantity: int | None = None,
938
+ bundle: dict[str, int] | None = None,
939
+ ) -> HyperAgentX402CheckoutResponse:
940
+ payload: dict[str, Any] = {}
941
+ if quantity is not None:
942
+ payload["quantity"] = int(quantity)
943
+ if bundle is not None:
944
+ payload["bundle"] = {str(k): int(v) for k, v in bundle.items()}
945
+ response = self._http._session.post(
946
+ f"{self._control_base_url}/x402/_bundle",
947
+ headers={"Authorization": f"Bearer {self._api_key}"},
948
+ json=payload,
949
+ )
950
+ response.raise_for_status()
951
+ return HyperAgentX402CheckoutResponse.from_dict(response.json())
952
+
953
+ def create_x402_checkout(
954
+ self,
955
+ *,
956
+ quantity: int | None = None,
957
+ bundle: dict[str, int] | None = None,
958
+ ) -> HyperAgentX402CheckoutResponse:
959
+ """Backward-compatible bundle x402 checkout shim."""
960
+ return self.purchase_bundle_via_x402(quantity=quantity, bundle=bundle)
961
+
912
962
  def discovery_health(self) -> Dict[str, Any]:
913
963
  response = self._http._session.get(f"{self._api_base_without_v1()}/discovery/health")
914
964
  response.raise_for_status()
@@ -1350,6 +1350,21 @@ class Deployments:
1350
1350
  payload = {"name": name} if name is not None else {}
1351
1351
  return self._post(f"{AGENTS_API_PREFIX}/{agent_id}/keys", json=payload or None)
1352
1352
 
1353
+ def purchase_entitlement_from_balance(
1354
+ self,
1355
+ plan_id: str,
1356
+ *,
1357
+ duration: int,
1358
+ tags: list[str] | None = None,
1359
+ ) -> dict:
1360
+ payload: dict[str, Any] = {"duration": int(duration)}
1361
+ if tags is not None:
1362
+ payload["tags"] = list(tags)
1363
+ return self._post(f"/billing/balance/{quote(str(plan_id), safe='')}", json=payload)
1364
+
1365
+ def redeem_grant_code(self, code: str) -> dict:
1366
+ return self._post("/billing/grants/redeem", json={"code": str(code)})
1367
+
1353
1368
  def logs_token(self, agent_id: str) -> dict:
1354
1369
  """Mint a short-lived JWT token for backend log streaming."""
1355
1370
  return self._post(f"{AGENTS_API_PREFIX}/{agent_id}/logs/token")
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "hypercli-sdk"
7
- version = "2026.4.18"
7
+ version = "2026.4.20"
8
8
  description = "Python SDK for HyperCLI - GPU orchestration and HyperAgent API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1034,6 +1034,48 @@ def test_agents_create_scoped_key(agents_client):
1034
1034
  assert mock_client.post.call_args[1]["json"] == {"name": "agent-client"}
1035
1035
 
1036
1036
 
1037
+ def test_agents_purchase_entitlement_from_balance(agents_client):
1038
+ with patch("httpx.Client") as mock_client_class:
1039
+ mock_client = MagicMock()
1040
+ mock_response = Mock()
1041
+ mock_response.status_code = 200
1042
+ mock_response.json.return_value = {
1043
+ "grant": {"id": "grant-1", "type": "BALANCE", "duration": 3600},
1044
+ "entitlement": {"id": "ent-1", "plan_id": "1aiu"},
1045
+ }
1046
+ mock_client.post.return_value = mock_response
1047
+ mock_client.__enter__.return_value = mock_client
1048
+ mock_client.__exit__.return_value = False
1049
+ mock_client_class.return_value = mock_client
1050
+
1051
+ result = agents_client.purchase_entitlement_from_balance("1aiu", duration=3600, tags=["customer=acme"])
1052
+
1053
+ assert result["grant"]["type"] == "BALANCE"
1054
+ assert mock_client.post.call_args[0][0].endswith("/billing/balance/1aiu")
1055
+ assert mock_client.post.call_args[1]["json"] == {"duration": 3600, "tags": ["customer=acme"]}
1056
+
1057
+
1058
+ def test_agents_redeem_grant_code(agents_client):
1059
+ with patch("httpx.Client") as mock_client_class:
1060
+ mock_client = MagicMock()
1061
+ mock_response = Mock()
1062
+ mock_response.status_code = 200
1063
+ mock_response.json.return_value = {
1064
+ "grant": {"id": "grant-1", "type": "ACTIVATION_CODE", "code": "promo-123"},
1065
+ "entitlement": {"id": "ent-1", "plan_id": "1aiu"},
1066
+ }
1067
+ mock_client.post.return_value = mock_response
1068
+ mock_client.__enter__.return_value = mock_client
1069
+ mock_client.__exit__.return_value = False
1070
+ mock_client_class.return_value = mock_client
1071
+
1072
+ result = agents_client.redeem_grant_code("promo-123")
1073
+
1074
+ assert result["grant"]["code"] == "promo-123"
1075
+ assert mock_client.post.call_args[0][0].endswith("/billing/grants/redeem")
1076
+ assert mock_client.post.call_args[1]["json"] == {"code": "promo-123"}
1077
+
1078
+
1037
1079
  def test_openclaw_agent_resolve_gateway_token_uses_inference_endpoint():
1038
1080
  manager = Mock()
1039
1081
  manager.inference_token.return_value = {
@@ -12,6 +12,7 @@ from hypercli.agent import (
12
12
  HyperAgentPlan,
13
13
  HyperAgentCurrentPlan,
14
14
  HyperAgentSubscription,
15
+ HyperAgentEntitlement,
15
16
  HyperAgentSubscriptionSummary,
16
17
  HyperAgentModel,
17
18
  HyperAgentUsageSummary,
@@ -99,6 +100,23 @@ class TestHyperAgentDataclasses:
99
100
  "slot_inventory": {"large": {"granted": 2, "used": 1, "available": 1}},
100
101
  "active_entitlement_count": 1,
101
102
  },
103
+ "entitlement_items": [
104
+ {
105
+ "id": "ent-1",
106
+ "user_id": "user-1",
107
+ "subscription_id": "sub-1",
108
+ "plan_id": "large",
109
+ "plan_name": "Large",
110
+ "provider": "STRIPE",
111
+ "status": "ACTIVE",
112
+ "expires_at": "2026-04-15T00:00:00Z",
113
+ "agent_tier": "large",
114
+ "features": {"voice": True},
115
+ "tags": ["customer=acme"],
116
+ "active_agent_count": 1,
117
+ "active_agent_ids": ["agent-1"],
118
+ }
119
+ ],
102
120
  "active_subscriptions": [
103
121
  {
104
122
  "id": "sub-1",
@@ -369,6 +387,90 @@ class TestHyperAgentClient:
369
387
  max_tokens=100
370
388
  )
371
389
 
390
+ def test_purchase_via_x402_uses_plan_route(self, mock_http):
391
+ agent = HyperAgent(
392
+ mock_http,
393
+ agent_api_key="sk-hyper-test",
394
+ agents_api_base_url="https://api.hypercli.com/agents",
395
+ )
396
+ mock_response = Mock()
397
+ mock_response.raise_for_status.return_value = None
398
+ mock_response.json.return_value = {
399
+ "ok": True,
400
+ "key": "hyper_api_x402",
401
+ "plan_id": "1aiu",
402
+ "quantity": 1,
403
+ "bundle": {"small": 1},
404
+ "amount_paid": "20.00",
405
+ "duration_days": 30,
406
+ "expires_at": "2026-05-19T12:00:00Z",
407
+ "tpm_limit": 1000,
408
+ "rpm_limit": 10,
409
+ }
410
+ mock_http._session.post.return_value = mock_response
411
+
412
+ result = agent.purchase_via_x402("1aiu", quantity=1, bundle={"small": 1})
413
+
414
+ assert result.plan_id == "1aiu"
415
+ assert mock_http._session.post.call_args[0][0] == "https://api.hypercli.com/agents/x402/1aiu"
416
+ assert mock_http._session.post.call_args[1]["json"] == {"quantity": 1, "bundle": {"small": 1}}
417
+
418
+ def test_purchase_bundle_via_x402_uses_bundle_route(self, mock_http):
419
+ agent = HyperAgent(
420
+ mock_http,
421
+ agent_api_key="sk-hyper-test",
422
+ agents_api_base_url="https://api.hypercli.com/agents",
423
+ )
424
+ mock_response = Mock()
425
+ mock_response.raise_for_status.return_value = None
426
+ mock_response.json.return_value = {
427
+ "ok": True,
428
+ "key": "hyper_api_x402",
429
+ "plan_id": "_bundle",
430
+ "quantity": 1,
431
+ "bundle": {"large": 2},
432
+ "amount_paid": "200.00",
433
+ "duration_days": 30,
434
+ "expires_at": "2026-05-19T12:00:00Z",
435
+ "tpm_limit": 1000,
436
+ "rpm_limit": 10,
437
+ }
438
+ mock_http._session.post.return_value = mock_response
439
+
440
+ result = agent.purchase_bundle_via_x402(quantity=1, bundle={"large": 2})
441
+
442
+ assert result.plan_id == "_bundle"
443
+ assert mock_http._session.post.call_args[0][0] == "https://api.hypercli.com/agents/x402/_bundle"
444
+ assert mock_http._session.post.call_args[1]["json"] == {"quantity": 1, "bundle": {"large": 2}}
445
+
446
+ def test_create_x402_checkout_is_bundle_shim(self, mock_http):
447
+ agent = HyperAgent(
448
+ mock_http,
449
+ agent_api_key="sk-hyper-test",
450
+ agents_api_base_url="https://api.hypercli.com/agents",
451
+ )
452
+ mock_response = Mock()
453
+ mock_response.raise_for_status.return_value = None
454
+ mock_response.json.return_value = {
455
+ "ok": True,
456
+ "key": "hyper_api_x402",
457
+ "plan_id": "_bundle",
458
+ "quantity": 1,
459
+ "bundle": {"medium": 1},
460
+ "amount_paid": "40.00",
461
+ "duration_days": 30,
462
+ "expires_at": "2026-05-19T12:00:00Z",
463
+ "tpm_limit": 1000,
464
+ "rpm_limit": 10,
465
+ }
466
+ mock_http._session.post.return_value = mock_response
467
+
468
+ result = agent.create_x402_checkout(quantity=1, bundle={"medium": 1})
469
+
470
+ assert result.plan_id == "_bundle"
471
+ assert mock_http._session.post.call_args[0][0] == "https://api.hypercli.com/agents/x402/_bundle"
472
+ assert mock_http._session.post.call_args[1]["json"] == {"quantity": 1, "bundle": {"medium": 1}}
473
+
372
474
  class TestHyperAgentIntegration:
373
475
  """Integration tests for HyperAgent client (require running service)."""
374
476