asiflow-hyper 1.0.0__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.
- asiflow_hyper-1.0.0/PKG-INFO +37 -0
- asiflow_hyper-1.0.0/README.md +20 -0
- asiflow_hyper-1.0.0/asiflow_hyper/__init__.py +6 -0
- asiflow_hyper-1.0.0/asiflow_hyper/auth.py +91 -0
- asiflow_hyper-1.0.0/asiflow_hyper/client.py +125 -0
- asiflow_hyper-1.0.0/asiflow_hyper.egg-info/PKG-INFO +37 -0
- asiflow_hyper-1.0.0/asiflow_hyper.egg-info/SOURCES.txt +10 -0
- asiflow_hyper-1.0.0/asiflow_hyper.egg-info/dependency_links.txt +1 -0
- asiflow_hyper-1.0.0/asiflow_hyper.egg-info/requires.txt +1 -0
- asiflow_hyper-1.0.0/asiflow_hyper.egg-info/top_level.txt +1 -0
- asiflow_hyper-1.0.0/pyproject.toml +23 -0
- asiflow_hyper-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: asiflow-hyper
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python client for the Hyper Agent API (OAuth 2.1, REST v1): delegate objectives, run and verify work, read evidence.
|
|
5
|
+
Author: ASIFlow
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://hyper.asiflow.ai
|
|
8
|
+
Project-URL: Documentation, https://hyper.asiflow.ai/docs/platform/agent-api
|
|
9
|
+
Project-URL: OpenAPI, https://api.hyper.asiflow.ai/openapi.json
|
|
10
|
+
Keywords: hyper,agent,ai software engineer,oauth,mcp
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: httpx>=0.25
|
|
17
|
+
|
|
18
|
+
# asiflow-hyper
|
|
19
|
+
|
|
20
|
+
Python client for the [Hyper Agent API](https://hyper.asiflow.ai/docs/platform/agent-api): delegate a software objective to Hyper, run and verify the work, read the evidence. Thin and honest: every method is one documented REST call (`https://api.hyper.asiflow.ai/openapi.json`); errors are the API's RFC 9457 problems.
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from asiflow_hyper import HyperClient, device_login
|
|
24
|
+
|
|
25
|
+
token = device_login(client_name="my-agent", scope="objectives:read objectives:write runs:write evidence:read deploy:verify offline_access")
|
|
26
|
+
hyper = HyperClient(token=token.access_token)
|
|
27
|
+
|
|
28
|
+
job = hyper.create_objective(name="Bakery site", objective="A one-page site for Nour's bakery with a contact form.")
|
|
29
|
+
job = hyper.wait_job(job["id"])
|
|
30
|
+
run = hyper.start_run(job["result"]["project_id"], policy={"budget_cap_usd": 10})
|
|
31
|
+
run = hyper.wait_run(run["result"]["run_id"], on_needs_you=lambda card: "approved")
|
|
32
|
+
for attempt in run["attempts"]:
|
|
33
|
+
if attempt["status"] == "verified":
|
|
34
|
+
print(hyper.attempt_evidence(attempt["id"]))
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The `agent-interop-e2e` lane runs this client against production on every Hyper deploy.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# asiflow-hyper
|
|
2
|
+
|
|
3
|
+
Python client for the [Hyper Agent API](https://hyper.asiflow.ai/docs/platform/agent-api): delegate a software objective to Hyper, run and verify the work, read the evidence. Thin and honest: every method is one documented REST call (`https://api.hyper.asiflow.ai/openapi.json`); errors are the API's RFC 9457 problems.
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
from asiflow_hyper import HyperClient, device_login
|
|
7
|
+
|
|
8
|
+
token = device_login(client_name="my-agent", scope="objectives:read objectives:write runs:write evidence:read deploy:verify offline_access")
|
|
9
|
+
hyper = HyperClient(token=token.access_token)
|
|
10
|
+
|
|
11
|
+
job = hyper.create_objective(name="Bakery site", objective="A one-page site for Nour's bakery with a contact form.")
|
|
12
|
+
job = hyper.wait_job(job["id"])
|
|
13
|
+
run = hyper.start_run(job["result"]["project_id"], policy={"budget_cap_usd": 10})
|
|
14
|
+
run = hyper.wait_run(run["result"]["run_id"], on_needs_you=lambda card: "approved")
|
|
15
|
+
for attempt in run["attempts"]:
|
|
16
|
+
if attempt["status"] == "verified":
|
|
17
|
+
print(hyper.attempt_evidence(attempt["id"]))
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The `agent-interop-e2e` lane runs this client against production on every Hyper deploy.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""asiflow-hyper — Python client for the Hyper Agent API."""
|
|
2
|
+
from .auth import DeviceToken, TokenSet, device_login, jwt_bearer_login, refresh, register_client
|
|
3
|
+
from .client import HyperClient, HyperError
|
|
4
|
+
|
|
5
|
+
__all__ = ["HyperClient", "HyperError", "DeviceToken", "TokenSet", "device_login", "jwt_bearer_login", "refresh", "register_client"]
|
|
6
|
+
__version__ = "1.0.0"
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""OAuth 2.1 helpers for the Hyper authorization server (RFC 7591 registration,
|
|
2
|
+
RFC 8628 device grant, RFC 7523 jwt-bearer, rotating refresh). Human-facing:
|
|
3
|
+
``device_login`` prints the code the person types at hyper.asiflow.ai."""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import time
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Callable, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
DEFAULT_BASE = "https://api.hyper.asiflow.ai"
|
|
13
|
+
DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code"
|
|
14
|
+
JWT_BEARER_GRANT = "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class TokenSet:
|
|
19
|
+
access_token: str
|
|
20
|
+
refresh_token: Optional[str]
|
|
21
|
+
expires_in: int
|
|
22
|
+
scope: str
|
|
23
|
+
token_type: str = "Bearer"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class DeviceToken(TokenSet):
|
|
28
|
+
client_id: str = ""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class AuthError(Exception):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _post_form(base: str, path: str, data: Dict[str, str], transport: Optional[httpx.BaseTransport] = None) -> Dict:
|
|
36
|
+
with httpx.Client(base_url=base.rstrip("/"), timeout=30.0, transport=transport) as c:
|
|
37
|
+
r = c.post(path, data=data, headers={"Accept": "application/json"})
|
|
38
|
+
body = r.json() if r.content else {}
|
|
39
|
+
if r.status_code >= 400:
|
|
40
|
+
raise AuthError(f"{r.status_code} {body.get('error')}: {body.get('error_description') or body}")
|
|
41
|
+
return body
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def register_client(*, client_name: str, scope: str, grant_types: Optional[List[str]] = None,
|
|
45
|
+
redirect_uris: Optional[List[str]] = None, base: str = DEFAULT_BASE, transport: Optional[httpx.BaseTransport] = None) -> Dict:
|
|
46
|
+
"""RFC 7591 dynamic registration. Unverified clients receive read-only scopes until reviewed."""
|
|
47
|
+
body = {"client_name": client_name, "scope": scope, "grant_types": grant_types or [DEVICE_GRANT, "refresh_token"],
|
|
48
|
+
"redirect_uris": redirect_uris or ["https://hyper.asiflow.ai/oauth/callback"]}
|
|
49
|
+
with httpx.Client(base_url=base.rstrip("/"), timeout=30.0, transport=transport) as c:
|
|
50
|
+
r = c.post("/oauth/register", json=body, headers={"Accept": "application/json"})
|
|
51
|
+
out = r.json() if r.content else {}
|
|
52
|
+
if r.status_code != 201:
|
|
53
|
+
raise AuthError(f"register → {r.status_code} {out}")
|
|
54
|
+
return out
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def device_login(*, client_name: str, scope: str, client_id: Optional[str] = None, base: str = DEFAULT_BASE,
|
|
58
|
+
prompt: Callable[[str, str], None] = None, transport: Optional[httpx.BaseTransport] = None, timeout_s: float = 600) -> DeviceToken:
|
|
59
|
+
"""Register (unless client_id is given), start the device grant, show the person the
|
|
60
|
+
code, poll until they approve. ``prompt(verification_uri, user_code)`` defaults to print."""
|
|
61
|
+
cid = client_id or register_client(client_name=client_name, scope=scope, base=base, transport=transport)["client_id"]
|
|
62
|
+
dev = _post_form(base, "/oauth/device", {"client_id": cid, "scope": scope}, transport)
|
|
63
|
+
(prompt or (lambda uri, code: print(f"Open {uri} and enter the code {code}")))(dev.get("verification_uri_complete") or dev["verification_uri"], dev["user_code"])
|
|
64
|
+
interval, deadline = float(dev.get("interval") or 5), time.time() + timeout_s
|
|
65
|
+
while time.time() < deadline:
|
|
66
|
+
time.sleep(interval)
|
|
67
|
+
try:
|
|
68
|
+
tok = _post_form(base, "/oauth/token", {"grant_type": DEVICE_GRANT, "client_id": cid, "device_code": dev["device_code"]}, transport)
|
|
69
|
+
except AuthError as exc:
|
|
70
|
+
msg = str(exc)
|
|
71
|
+
if "authorization_pending" in msg:
|
|
72
|
+
continue
|
|
73
|
+
if "slow_down" in msg:
|
|
74
|
+
interval += 5
|
|
75
|
+
continue
|
|
76
|
+
raise
|
|
77
|
+
return DeviceToken(access_token=tok["access_token"], refresh_token=tok.get("refresh_token"), expires_in=int(tok.get("expires_in") or 900),
|
|
78
|
+
scope=str(tok.get("scope") or scope), client_id=cid)
|
|
79
|
+
raise AuthError("the person did not approve the device code in time")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def jwt_bearer_login(*, client_id: str, assertion: str, scope: str, base: str = DEFAULT_BASE, transport: Optional[httpx.BaseTransport] = None) -> TokenSet:
|
|
83
|
+
"""RFC 7523: exchange a signed assertion (a key Hyper trusts for this client) for an access token."""
|
|
84
|
+
tok = _post_form(base, "/oauth/token", {"grant_type": JWT_BEARER_GRANT, "client_id": client_id, "assertion": assertion, "scope": scope}, transport)
|
|
85
|
+
return TokenSet(access_token=tok["access_token"], refresh_token=tok.get("refresh_token"), expires_in=int(tok.get("expires_in") or 900), scope=str(tok.get("scope") or scope))
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def refresh(*, client_id: str, refresh_token: str, base: str = DEFAULT_BASE, transport: Optional[httpx.BaseTransport] = None) -> TokenSet:
|
|
89
|
+
"""Rotating refresh: the old token is invalid after this call; reuse revokes the family."""
|
|
90
|
+
tok = _post_form(base, "/oauth/token", {"grant_type": "refresh_token", "client_id": client_id, "refresh_token": refresh_token}, transport)
|
|
91
|
+
return TokenSet(access_token=tok["access_token"], refresh_token=tok.get("refresh_token"), expires_in=int(tok.get("expires_in") or 900), scope=str(tok.get("scope") or ""))
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""The REST v1 client. One method per documented call; nothing invented."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import time
|
|
5
|
+
import uuid
|
|
6
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
DEFAULT_BASE = "https://api.hyper.asiflow.ai"
|
|
11
|
+
TERMINAL_JOB = ("succeeded", "failed", "cancelled")
|
|
12
|
+
TERMINAL_RUN = ("completed", "failed", "stopped")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class HyperError(Exception):
|
|
16
|
+
"""An RFC 9457 problem from the API: ``status``, ``type``, ``title``, ``detail``."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, status: int, problem: Dict[str, Any]):
|
|
19
|
+
self.status, self.problem = status, problem
|
|
20
|
+
super().__init__(f"{status} {problem.get('title') or ''}: {problem.get('detail') or problem}")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class HyperClient:
|
|
24
|
+
def __init__(self, token: str, *, base_url: str = DEFAULT_BASE, timeout: float = 30.0, transport: Optional[httpx.BaseTransport] = None,
|
|
25
|
+
user_agent: str = "asiflow-hyper-python/1.0.0"):
|
|
26
|
+
self.base_url = base_url.rstrip("/")
|
|
27
|
+
self._http = httpx.Client(base_url=self.base_url, timeout=timeout, transport=transport,
|
|
28
|
+
headers={"Authorization": f"Bearer {token}", "User-Agent": user_agent, "Accept": "application/json"})
|
|
29
|
+
|
|
30
|
+
# ── transport ──────────────────────────────────────────────────────────
|
|
31
|
+
def _call(self, method: str, path: str, *, json: Any = None, idempotent: bool = False) -> Any:
|
|
32
|
+
headers = {"Idempotency-Key": str(uuid.uuid4())} if idempotent else {}
|
|
33
|
+
r = self._http.request(method, path, json=json, headers=headers)
|
|
34
|
+
if r.status_code >= 400:
|
|
35
|
+
try:
|
|
36
|
+
problem = r.json()
|
|
37
|
+
except ValueError:
|
|
38
|
+
problem = {"title": r.reason_phrase, "detail": r.text[:300]}
|
|
39
|
+
raise HyperError(r.status_code, problem)
|
|
40
|
+
return r.json() if r.content else None
|
|
41
|
+
|
|
42
|
+
def close(self) -> None:
|
|
43
|
+
self._http.close()
|
|
44
|
+
|
|
45
|
+
# ── identity ───────────────────────────────────────────────────────────
|
|
46
|
+
def me(self) -> Dict[str, Any]:
|
|
47
|
+
return self._call("GET", "/v1/me")
|
|
48
|
+
|
|
49
|
+
# ── objectives and jobs ────────────────────────────────────────────────
|
|
50
|
+
def create_objective(self, *, name: str, objective: str, tasks: Optional[List[Dict[str, Any]]] = None,
|
|
51
|
+
autonomy: str = "balanced", repo: Optional[str] = None) -> Dict[str, Any]:
|
|
52
|
+
body: Dict[str, Any] = {"name": name, "objective": objective, "autonomy": autonomy}
|
|
53
|
+
if tasks is not None:
|
|
54
|
+
body["tasks"] = tasks
|
|
55
|
+
if repo:
|
|
56
|
+
body["repo"] = repo
|
|
57
|
+
return self._call("POST", "/v1/objectives", json=body, idempotent=True)
|
|
58
|
+
|
|
59
|
+
def get_job(self, job_id: str) -> Dict[str, Any]:
|
|
60
|
+
return self._call("GET", f"/v1/jobs/{job_id}")
|
|
61
|
+
|
|
62
|
+
def cancel_job(self, job_id: str) -> Dict[str, Any]:
|
|
63
|
+
return self._call("POST", f"/v1/jobs/{job_id}/cancel")
|
|
64
|
+
|
|
65
|
+
def wait_job(self, job_id: str, *, timeout_s: float = 900, interval_s: float = 5.0) -> Dict[str, Any]:
|
|
66
|
+
deadline = time.time() + timeout_s
|
|
67
|
+
while True:
|
|
68
|
+
job = self.get_job(job_id)
|
|
69
|
+
if job.get("terminal") or job.get("status") in TERMINAL_JOB + ("waiting_for_acceptance",):
|
|
70
|
+
return job
|
|
71
|
+
if time.time() > deadline:
|
|
72
|
+
raise TimeoutError(f"job {job_id} still {job.get('status')} after {timeout_s}s")
|
|
73
|
+
time.sleep(interval_s)
|
|
74
|
+
|
|
75
|
+
# ── projects, repos, runs ──────────────────────────────────────────────
|
|
76
|
+
def list_projects(self) -> Dict[str, Any]:
|
|
77
|
+
return self._call("GET", "/v1/projects")
|
|
78
|
+
|
|
79
|
+
def get_project(self, project_id: str) -> Dict[str, Any]:
|
|
80
|
+
return self._call("GET", f"/v1/projects/{project_id}")
|
|
81
|
+
|
|
82
|
+
def connect_repo(self, project_id: str, repo: str) -> Dict[str, Any]:
|
|
83
|
+
return self._call("POST", f"/v1/projects/{project_id}/repo", json={"repo": repo})
|
|
84
|
+
|
|
85
|
+
def start_run(self, project_id: str, *, scope: Optional[Dict[str, Any]] = None, policy: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
86
|
+
return self._call("POST", f"/v1/projects/{project_id}/runs", json={"scope": scope or {"kind": "project"}, "policy": policy or {}}, idempotent=True)
|
|
87
|
+
|
|
88
|
+
def get_run(self, run_id: str) -> Dict[str, Any]:
|
|
89
|
+
return self._call("GET", f"/v1/runs/{run_id}")
|
|
90
|
+
|
|
91
|
+
def control_run(self, run_id: str, action: str) -> Dict[str, Any]:
|
|
92
|
+
if action not in ("pause", "resume", "stop"):
|
|
93
|
+
raise ValueError("action must be pause, resume or stop")
|
|
94
|
+
return self._call("POST", f"/v1/runs/{run_id}/{action}")
|
|
95
|
+
|
|
96
|
+
def wait_run(self, run_id: str, *, timeout_s: float = 1800, interval_s: float = 10.0,
|
|
97
|
+
on_needs_you: Optional[Callable[[Dict[str, Any]], Any]] = None) -> Dict[str, Any]:
|
|
98
|
+
"""Poll until the run ends. When it blocks on a card and ``on_needs_you`` is given,
|
|
99
|
+
the callback returns "approved" | "rejected" | "changes_requested" or (status, note)."""
|
|
100
|
+
deadline = time.time() + timeout_s
|
|
101
|
+
while True:
|
|
102
|
+
run = self.get_run(run_id)
|
|
103
|
+
if run.get("status") in TERMINAL_RUN:
|
|
104
|
+
return run
|
|
105
|
+
if run.get("status") == "blocked" and run.get("needs_you") and on_needs_you is not None:
|
|
106
|
+
for card in run["needs_you"]:
|
|
107
|
+
answer = on_needs_you(card)
|
|
108
|
+
status, note = (answer, None) if isinstance(answer, str) else (answer[0], answer[1])
|
|
109
|
+
self.decide(card["id"], status=status, note=note)
|
|
110
|
+
if time.time() > deadline:
|
|
111
|
+
raise TimeoutError(f"run {run_id} still {run.get('status')} after {timeout_s}s")
|
|
112
|
+
time.sleep(interval_s)
|
|
113
|
+
|
|
114
|
+
# ── approvals and evidence ─────────────────────────────────────────────
|
|
115
|
+
def list_approvals(self, project_id: str) -> Dict[str, Any]:
|
|
116
|
+
return self._call("GET", f"/v1/projects/{project_id}/approvals")
|
|
117
|
+
|
|
118
|
+
def decide(self, approval_id: str, *, status: str, note: Optional[str] = None) -> Dict[str, Any]:
|
|
119
|
+
return self._call("POST", f"/v1/approvals/{approval_id}/decide", json={"status": status, "note": note})
|
|
120
|
+
|
|
121
|
+
def attempt_evidence(self, attempt_id: str) -> Dict[str, Any]:
|
|
122
|
+
return self._call("GET", f"/v1/attempts/{attempt_id}/evidence")
|
|
123
|
+
|
|
124
|
+
def verify_deployment(self, url: str, *, expect_text: Optional[str] = None) -> Dict[str, Any]:
|
|
125
|
+
return self._call("POST", "/v1/verify/deployment", json={"url": url, "expect_text": expect_text})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: asiflow-hyper
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python client for the Hyper Agent API (OAuth 2.1, REST v1): delegate objectives, run and verify work, read evidence.
|
|
5
|
+
Author: ASIFlow
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://hyper.asiflow.ai
|
|
8
|
+
Project-URL: Documentation, https://hyper.asiflow.ai/docs/platform/agent-api
|
|
9
|
+
Project-URL: OpenAPI, https://api.hyper.asiflow.ai/openapi.json
|
|
10
|
+
Keywords: hyper,agent,ai software engineer,oauth,mcp
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: httpx>=0.25
|
|
17
|
+
|
|
18
|
+
# asiflow-hyper
|
|
19
|
+
|
|
20
|
+
Python client for the [Hyper Agent API](https://hyper.asiflow.ai/docs/platform/agent-api): delegate a software objective to Hyper, run and verify the work, read the evidence. Thin and honest: every method is one documented REST call (`https://api.hyper.asiflow.ai/openapi.json`); errors are the API's RFC 9457 problems.
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from asiflow_hyper import HyperClient, device_login
|
|
24
|
+
|
|
25
|
+
token = device_login(client_name="my-agent", scope="objectives:read objectives:write runs:write evidence:read deploy:verify offline_access")
|
|
26
|
+
hyper = HyperClient(token=token.access_token)
|
|
27
|
+
|
|
28
|
+
job = hyper.create_objective(name="Bakery site", objective="A one-page site for Nour's bakery with a contact form.")
|
|
29
|
+
job = hyper.wait_job(job["id"])
|
|
30
|
+
run = hyper.start_run(job["result"]["project_id"], policy={"budget_cap_usd": 10})
|
|
31
|
+
run = hyper.wait_run(run["result"]["run_id"], on_needs_you=lambda card: "approved")
|
|
32
|
+
for attempt in run["attempts"]:
|
|
33
|
+
if attempt["status"] == "verified":
|
|
34
|
+
print(hyper.attempt_evidence(attempt["id"]))
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The `agent-interop-e2e` lane runs this client against production on every Hyper deploy.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
asiflow_hyper/__init__.py
|
|
4
|
+
asiflow_hyper/auth.py
|
|
5
|
+
asiflow_hyper/client.py
|
|
6
|
+
asiflow_hyper.egg-info/PKG-INFO
|
|
7
|
+
asiflow_hyper.egg-info/SOURCES.txt
|
|
8
|
+
asiflow_hyper.egg-info/dependency_links.txt
|
|
9
|
+
asiflow_hyper.egg-info/requires.txt
|
|
10
|
+
asiflow_hyper.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
httpx>=0.25
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
asiflow_hyper
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "asiflow-hyper"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Python client for the Hyper Agent API (OAuth 2.1, REST v1): delegate objectives, run and verify work, read evidence."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [{ name = "ASIFlow" }]
|
|
13
|
+
dependencies = ["httpx>=0.25"]
|
|
14
|
+
keywords = ["hyper", "agent", "ai software engineer", "oauth", "mcp"]
|
|
15
|
+
classifiers = ["Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent"]
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
Homepage = "https://hyper.asiflow.ai"
|
|
19
|
+
Documentation = "https://hyper.asiflow.ai/docs/platform/agent-api"
|
|
20
|
+
OpenAPI = "https://api.hyper.asiflow.ai/openapi.json"
|
|
21
|
+
|
|
22
|
+
[tool.setuptools.packages.find]
|
|
23
|
+
include = ["asiflow_hyper*"]
|