hypercli-sdk 2026.4.21__tar.gz → 2026.4.22__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.21 → hypercli_sdk-2026.4.22}/PKG-INFO +1 -1
  2. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/hypercli/__init__.py +1 -1
  3. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/hypercli/openclaw/gateway.py +20 -0
  4. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/pyproject.toml +1 -1
  5. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/tests/test_gateway.py +87 -0
  6. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/.gitignore +0 -0
  7. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/README.md +0 -0
  8. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/hypercli/agent.py +0 -0
  9. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/hypercli/agents.py +0 -0
  10. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/hypercli/billing.py +0 -0
  11. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/hypercli/client.py +0 -0
  12. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/hypercli/config.py +0 -0
  13. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/hypercli/files.py +0 -0
  14. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/hypercli/gateway.py +0 -0
  15. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/hypercli/http.py +0 -0
  16. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/hypercli/instances.py +0 -0
  17. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/hypercli/job/__init__.py +0 -0
  18. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/hypercli/job/base.py +0 -0
  19. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/hypercli/job/comfyui.py +0 -0
  20. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/hypercli/job/gradio.py +0 -0
  21. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/hypercli/jobs.py +0 -0
  22. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/hypercli/keys.py +0 -0
  23. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/hypercli/logs.py +0 -0
  24. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/hypercli/models.py +0 -0
  25. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/hypercli/openclaw/__init__.py +0 -0
  26. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/hypercli/renders.py +0 -0
  27. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/hypercli/shell.py +0 -0
  28. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/hypercli/user.py +0 -0
  29. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/hypercli/voice.py +0 -0
  30. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/hypercli/x402.py +0 -0
  31. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/tests/integration/conftest.py +0 -0
  32. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/tests/integration/test_agents.py +0 -0
  33. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/tests/integration/test_auth.py +0 -0
  34. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/tests/integration/test_billing.py +0 -0
  35. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/tests/integration/test_instances.py +0 -0
  36. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/tests/integration/test_jobs_dryrun.py +0 -0
  37. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/tests/integration/test_keys.py +0 -0
  38. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/tests/integration/test_renders.py +0 -0
  39. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/tests/test_agents.py +0 -0
  40. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/tests/test_apply_params.py +0 -0
  41. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/tests/test_bootstrap_console_test_key.py +0 -0
  42. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/tests/test_bootstrap_dev_test_keys.py +0 -0
  43. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/tests/test_claw.py +0 -0
  44. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/tests/test_config.py +0 -0
  45. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/tests/test_exec_shell_dryrun.py +0 -0
  46. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/tests/test_gateway_retry.py +0 -0
  47. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/tests/test_graph_to_api.py +0 -0
  48. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/tests/test_http.py +0 -0
  49. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/tests/test_jobs.py +0 -0
  50. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/tests/test_keys.py +0 -0
  51. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/tests/test_models.py +0 -0
  52. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/tests/test_renders_subscription.py +0 -0
  53. {hypercli_sdk-2026.4.21 → hypercli_sdk-2026.4.22}/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.21
3
+ Version: 2026.4.22
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.21"
81
+ __version__ = "2026.4.22"
82
82
  __all__ = [
83
83
  "HyperCLI",
84
84
  "configure",
@@ -300,6 +300,10 @@ def _read_connect_pairing_request_id(details: Any) -> str | None:
300
300
  return request_id.strip() if isinstance(request_id, str) and request_id.strip() else None
301
301
 
302
302
 
303
+ def _is_concurrent_pairing_approval_race(exc: Exception) -> bool:
304
+ return "unknown requestid" in str(exc).lower()
305
+
306
+
303
307
  def _is_retryable_connect_error(exc: Exception) -> bool:
304
308
  status_code = getattr(exc, "status_code", None)
305
309
  response = getattr(exc, "response", None)
@@ -1081,6 +1085,22 @@ class GatewayClient:
1081
1085
  delay = min(delay * BACKOFF_MULTIPLIER, MAX_RECONNECT_DELAY)
1082
1086
  continue
1083
1087
  except Exception as approval_error:
1088
+ if _is_concurrent_pairing_approval_race(approval_error):
1089
+ self._update_pairing_state(
1090
+ GatewayPairingState(
1091
+ request_id=request_id,
1092
+ role=OPERATOR_ROLE,
1093
+ gateway_url=self.url,
1094
+ device_id=identity.device_id,
1095
+ status="approved",
1096
+ updated_at_ms=_now_ms(),
1097
+ )
1098
+ )
1099
+ if ws is not None:
1100
+ await ws.close()
1101
+ await asyncio.sleep(delay)
1102
+ delay = min(delay * BACKOFF_MULTIPLIER, MAX_RECONNECT_DELAY)
1103
+ continue
1084
1104
  self._update_pairing_state(
1085
1105
  GatewayPairingState(
1086
1106
  request_id=request_id,
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "hypercli-sdk"
7
- version = "2026.4.21"
7
+ version = "2026.4.22"
8
8
  description = "Python SDK for HyperCLI - GPU orchestration and HyperAgent API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -122,6 +122,93 @@ async def test_connect_auto_approves_pairing_and_reconnects(monkeypatch: pytest.
122
122
  await client.close()
123
123
 
124
124
 
125
+ @pytest.mark.asyncio
126
+ async def test_connect_treats_unknown_request_id_as_concurrent_pairing_approval(monkeypatch: pytest.MonkeyPatch) -> None:
127
+ sockets: list[MockConnection] = []
128
+
129
+ async def fake_connect(*args, **kwargs):
130
+ conn = MockConnection()
131
+ sockets.append(conn)
132
+ return conn
133
+
134
+ approvals: list[tuple[str, str]] = []
135
+
136
+ async def fake_approve(self: GatewayClient, request_id: str) -> None:
137
+ approvals.append((self.deployment_id or "", request_id))
138
+ raise RuntimeError("unknown requestId")
139
+
140
+ monkeypatch.setattr("hypercli.openclaw.gateway.websockets.connect", fake_connect)
141
+ monkeypatch.setattr(GatewayClient, "_approve_pairing_request", fake_approve)
142
+
143
+ client = GatewayClient(
144
+ url="wss://openclaw-agent.example",
145
+ token="jwt-token",
146
+ gateway_token="gw-token",
147
+ deployment_id="deployment-123",
148
+ api_key="agent-key",
149
+ api_base="https://api.dev.hypercli.com/agents",
150
+ auto_approve_pairing=True,
151
+ )
152
+
153
+ connect_task = asyncio.create_task(client.connect())
154
+ while not sockets:
155
+ await asyncio.sleep(0)
156
+
157
+ first = sockets[0]
158
+ first.push({"type": "event", "event": "connect.challenge", "payload": {"nonce": "nonce-1"}})
159
+ while not first.sent:
160
+ await asyncio.sleep(0)
161
+ connect_request = first.sent[0]
162
+ first.push(
163
+ {
164
+ "type": "res",
165
+ "id": connect_request["id"],
166
+ "ok": False,
167
+ "error": {
168
+ "code": "INVALID_REQUEST",
169
+ "message": "pairing required",
170
+ "details": {
171
+ "code": "PAIRING_REQUIRED",
172
+ "requestId": "pairing-req-race",
173
+ },
174
+ },
175
+ }
176
+ )
177
+
178
+ while len(sockets) < 2:
179
+ await asyncio.sleep(0.05)
180
+
181
+ second = sockets[1]
182
+ second.push({"type": "event", "event": "connect.challenge", "payload": {"nonce": "nonce-2"}})
183
+ while not second.sent:
184
+ await asyncio.sleep(0)
185
+ reconnect_request = second.sent[0]
186
+ second.push(
187
+ {
188
+ "type": "res",
189
+ "id": reconnect_request["id"],
190
+ "ok": True,
191
+ "payload": {
192
+ "protocol": 3,
193
+ "server": {"version": "test"},
194
+ "auth": {
195
+ "deviceToken": "device-token-race",
196
+ "role": "operator",
197
+ "scopes": ["operator.admin"],
198
+ },
199
+ },
200
+ }
201
+ )
202
+
203
+ await connect_task
204
+
205
+ assert approvals == [("deployment-123", "pairing-req-race")]
206
+ assert client.is_connected is True
207
+ assert client.pending_pairing is None
208
+
209
+ await client.close()
210
+
211
+
125
212
  @pytest.mark.asyncio
126
213
  async def test_approve_pairing_request_uses_direct_local_pairing_api(monkeypatch: pytest.MonkeyPatch) -> None:
127
214
  captured: dict = {}