getstack 0.5.1__tar.gz → 0.7.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.
- {getstack-0.5.1 → getstack-0.7.0}/.gitignore +5 -0
- {getstack-0.5.1 → getstack-0.7.0}/PKG-INFO +1 -1
- {getstack-0.5.1 → getstack-0.7.0}/pyproject.toml +1 -1
- {getstack-0.5.1 → getstack-0.7.0}/src/getstack/__init__.py +57 -4
- getstack-0.7.0/src/getstack/_async/__init__.py +14 -0
- getstack-0.7.0/src/getstack/_async/agents.py +53 -0
- getstack-0.7.0/src/getstack/_async/audit.py +46 -0
- getstack-0.7.0/src/getstack/_async/credentials.py +43 -0
- getstack-0.7.0/src/getstack/_async/dropoffs.py +49 -0
- getstack-0.7.0/src/getstack/_async/identity.py +92 -0
- getstack-0.7.0/src/getstack/_async/intents.py +128 -0
- getstack-0.7.0/src/getstack/_async/missions.py +110 -0
- getstack-0.7.0/src/getstack/_async/notifications.py +49 -0
- getstack-0.7.0/src/getstack/_async/passports.py +364 -0
- getstack-0.7.0/src/getstack/_async/proxy.py +73 -0
- getstack-0.7.0/src/getstack/_async/reviews.py +53 -0
- getstack-0.7.0/src/getstack/_async/scan.py +40 -0
- getstack-0.7.0/src/getstack/_async/security_events.py +26 -0
- getstack-0.7.0/src/getstack/_async/services.py +41 -0
- getstack-0.7.0/src/getstack/_async/skills.py +162 -0
- {getstack-0.5.1 → getstack-0.7.0}/src/getstack/client.py +47 -13
- getstack-0.7.0/src/getstack/missions.py +130 -0
- {getstack-0.5.1 → getstack-0.7.0}/src/getstack/passports.py +7 -3
- {getstack-0.5.1 → getstack-0.7.0}/src/getstack/proxy.py +7 -1
- {getstack-0.5.1 → getstack-0.7.0}/src/getstack/skills.py +21 -1
- {getstack-0.5.1 → getstack-0.7.0}/CLAUDE.md +0 -0
- {getstack-0.5.1 → getstack-0.7.0}/LICENSE +0 -0
- {getstack-0.5.1 → getstack-0.7.0}/README.md +0 -0
- {getstack-0.5.1 → getstack-0.7.0}/src/getstack/agent_auth.py +0 -0
- {getstack-0.5.1 → getstack-0.7.0}/src/getstack/agents.py +0 -0
- {getstack-0.5.1 → getstack-0.7.0}/src/getstack/audit.py +0 -0
- {getstack-0.5.1 → getstack-0.7.0}/src/getstack/auth.py +0 -0
- {getstack-0.5.1 → getstack-0.7.0}/src/getstack/browser_bootstrap.py +0 -0
- {getstack-0.5.1 → getstack-0.7.0}/src/getstack/credentials.py +0 -0
- {getstack-0.5.1 → getstack-0.7.0}/src/getstack/dropoffs.py +0 -0
- {getstack-0.5.1 → getstack-0.7.0}/src/getstack/errors.py +0 -0
- {getstack-0.5.1 → getstack-0.7.0}/src/getstack/identity.py +0 -0
- {getstack-0.5.1 → getstack-0.7.0}/src/getstack/intents.py +0 -0
- {getstack-0.5.1 → getstack-0.7.0}/src/getstack/notifications.py +0 -0
- {getstack-0.5.1 → getstack-0.7.0}/src/getstack/py.typed +0 -0
- {getstack-0.5.1 → getstack-0.7.0}/src/getstack/reviews.py +0 -0
- {getstack-0.5.1 → getstack-0.7.0}/src/getstack/scan.py +0 -0
- {getstack-0.5.1 → getstack-0.7.0}/src/getstack/security_events.py +0 -0
- {getstack-0.5.1 → getstack-0.7.0}/src/getstack/services.py +0 -0
- {getstack-0.5.1 → getstack-0.7.0}/src/getstack/types.py +0 -0
|
@@ -60,6 +60,11 @@ scripts/.openapi-cache/
|
|
|
60
60
|
# Local dev screenshots
|
|
61
61
|
*-stripe.png
|
|
62
62
|
|
|
63
|
+
# Root-level scratch images (founder workspace — never commit)
|
|
64
|
+
/*.png
|
|
65
|
+
/*.jpg
|
|
66
|
+
/*.jpeg
|
|
67
|
+
|
|
63
68
|
# YC application session exports (founder voice + redacted creds — never commit)
|
|
64
69
|
docs/yc-q18/
|
|
65
70
|
docs/yc-application-session-preface.md
|
|
@@ -52,6 +52,7 @@ from .security_events import SecurityEventService as SecurityEventSvc
|
|
|
52
52
|
from .skills import SkillService
|
|
53
53
|
from .identity import IdentityService
|
|
54
54
|
from .intents import IntentsService
|
|
55
|
+
from .missions import MissionsService
|
|
55
56
|
from .types import (
|
|
56
57
|
Agent,
|
|
57
58
|
Passport,
|
|
@@ -216,6 +217,7 @@ class Stack:
|
|
|
216
217
|
self.skills = SkillService(self._client)
|
|
217
218
|
self.identity = IdentityService(self._client)
|
|
218
219
|
self.intents = IntentsService(self._client)
|
|
220
|
+
self.missions = MissionsService(self._client)
|
|
219
221
|
|
|
220
222
|
@classmethod
|
|
221
223
|
def from_session(cls, session_token: str, **kwargs) -> Stack:
|
|
@@ -256,15 +258,31 @@ class Stack:
|
|
|
256
258
|
class AsyncStack:
|
|
257
259
|
"""Asynchronous STACK client.
|
|
258
260
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
transport layer for future async service implementations.
|
|
261
|
+
Sub-chunk 2.4 — full parity with Stack. All 14 typed services are
|
|
262
|
+
async-native, plus AsyncMission for the bounded-mission context
|
|
263
|
+
manager. Uses httpx.AsyncClient under the hood.
|
|
263
264
|
|
|
264
265
|
Args:
|
|
265
266
|
api_key: A STACK API key (``sk_live_...``).
|
|
266
267
|
base_url: Override the API base URL.
|
|
267
268
|
timeout: HTTP timeout in seconds.
|
|
269
|
+
|
|
270
|
+
Example::
|
|
271
|
+
|
|
272
|
+
from getstack import AsyncStack
|
|
273
|
+
|
|
274
|
+
async with AsyncStack(api_key="sk_live_...") as stack:
|
|
275
|
+
agents = await stack.agents.list()
|
|
276
|
+
async with stack.passports.mission(
|
|
277
|
+
agent_id=agents[0].id,
|
|
278
|
+
intent="Process invoices",
|
|
279
|
+
services=["slack", "stripe"],
|
|
280
|
+
) as mission:
|
|
281
|
+
resp = await mission.proxy(
|
|
282
|
+
"stripe", "https://api.stripe.com/v1/charges",
|
|
283
|
+
method="POST", body={"amount": 50},
|
|
284
|
+
_proxy_service=stack.proxy,
|
|
285
|
+
)
|
|
268
286
|
"""
|
|
269
287
|
|
|
270
288
|
def __init__(
|
|
@@ -293,6 +311,41 @@ class AsyncStack:
|
|
|
293
311
|
|
|
294
312
|
self._client = AsyncHttpClient(auth, base_url=base_url, timeout=timeout)
|
|
295
313
|
|
|
314
|
+
# Sub-chunk 2.4 — 14 typed async services attached eagerly,
|
|
315
|
+
# matching Stack's posture. Footprint is small; lazy init
|
|
316
|
+
# would only buy us memory we don't need to save.
|
|
317
|
+
from ._async.agents import AsyncAgentService
|
|
318
|
+
from ._async.passports import AsyncPassportService
|
|
319
|
+
from ._async.credentials import AsyncCredentialService
|
|
320
|
+
from ._async.services import AsyncServiceService
|
|
321
|
+
from ._async.dropoffs import AsyncDropoffService
|
|
322
|
+
from ._async.reviews import AsyncReviewService
|
|
323
|
+
from ._async.notifications import AsyncNotificationService
|
|
324
|
+
from ._async.audit import AsyncAuditService
|
|
325
|
+
from ._async.proxy import AsyncProxyService
|
|
326
|
+
from ._async.scan import AsyncScanService
|
|
327
|
+
from ._async.security_events import AsyncSecurityEventService
|
|
328
|
+
from ._async.skills import AsyncSkillService
|
|
329
|
+
from ._async.identity import AsyncIdentityService
|
|
330
|
+
from ._async.intents import AsyncIntentsService
|
|
331
|
+
from ._async.missions import AsyncMissionsService
|
|
332
|
+
|
|
333
|
+
self.agents = AsyncAgentService(self._client)
|
|
334
|
+
self.passports = AsyncPassportService(self._client)
|
|
335
|
+
self.credentials = AsyncCredentialService(self._client)
|
|
336
|
+
self.services = AsyncServiceService(self._client)
|
|
337
|
+
self.dropoffs = AsyncDropoffService(self._client)
|
|
338
|
+
self.reviews = AsyncReviewService(self._client)
|
|
339
|
+
self.notifications = AsyncNotificationService(self._client)
|
|
340
|
+
self.audit = AsyncAuditService(self._client)
|
|
341
|
+
self.proxy = AsyncProxyService(self._client)
|
|
342
|
+
self.scan = AsyncScanService(self._client)
|
|
343
|
+
self.security_events = AsyncSecurityEventService(self._client)
|
|
344
|
+
self.skills = AsyncSkillService(self._client)
|
|
345
|
+
self.identity = AsyncIdentityService(self._client)
|
|
346
|
+
self.intents = AsyncIntentsService(self._client)
|
|
347
|
+
self.missions = AsyncMissionsService(self._client)
|
|
348
|
+
|
|
296
349
|
@classmethod
|
|
297
350
|
def from_session(cls, session_token: str, **kwargs) -> AsyncStack:
|
|
298
351
|
"""Create an async client authenticated with a session JWT."""
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Async service modules (Sub-chunk 2.4 — full parity with sync).
|
|
2
|
+
|
|
3
|
+
Each AsyncXxxService mirrors the sync XxxService surface exactly:
|
|
4
|
+
same method names, same kwargs, same return types. The only
|
|
5
|
+
difference is `async def` and `await self._client.x` instead of
|
|
6
|
+
`self._client.x`. The Mission ergonomic gets an AsyncMission
|
|
7
|
+
context manager that uses asyncio.create_task + asyncio.sleep
|
|
8
|
+
instead of threading.Timer.
|
|
9
|
+
|
|
10
|
+
Import via AsyncStack:
|
|
11
|
+
from getstack import AsyncStack
|
|
12
|
+
async with AsyncStack(api_key="sk_live_...") as stack:
|
|
13
|
+
agents = await stack.agents.list()
|
|
14
|
+
"""
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Async mirror of AgentService."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ..client import AsyncHttpClient
|
|
8
|
+
from ..types import Agent
|
|
9
|
+
from ..agents import _to_agent
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AsyncAgentService:
|
|
13
|
+
def __init__(self, client: AsyncHttpClient):
|
|
14
|
+
self._client = client
|
|
15
|
+
|
|
16
|
+
async def register(
|
|
17
|
+
self,
|
|
18
|
+
name: str,
|
|
19
|
+
description: str | None = None,
|
|
20
|
+
accountability_mode: str = "enforced",
|
|
21
|
+
on_warning: str = "notify",
|
|
22
|
+
on_critical: str = "notify",
|
|
23
|
+
) -> Agent:
|
|
24
|
+
body: dict[str, Any] = {"name": name, "accountability_mode": accountability_mode}
|
|
25
|
+
if description:
|
|
26
|
+
body["description"] = description
|
|
27
|
+
if on_warning != "notify":
|
|
28
|
+
body["on_warning"] = on_warning
|
|
29
|
+
if on_critical != "notify":
|
|
30
|
+
body["on_critical"] = on_critical
|
|
31
|
+
return _to_agent(await self._client.post("/v1/agents/register", json=body))
|
|
32
|
+
|
|
33
|
+
async def get(self, agent_id: str) -> Agent:
|
|
34
|
+
return _to_agent(await self._client.get(f"/v1/agents/{agent_id}"))
|
|
35
|
+
|
|
36
|
+
async def list(self) -> list[Agent]:
|
|
37
|
+
data = await self._client.get("/v1/agents")
|
|
38
|
+
return [_to_agent(a) for a in data]
|
|
39
|
+
|
|
40
|
+
async def update(self, agent_id: str, **kwargs: Any) -> Agent:
|
|
41
|
+
return _to_agent(await self._client.patch(f"/v1/agents/{agent_id}", json=kwargs))
|
|
42
|
+
|
|
43
|
+
async def suspend(self, agent_id: str) -> Agent:
|
|
44
|
+
return await self.update(agent_id, status="suspended")
|
|
45
|
+
|
|
46
|
+
async def activate(self, agent_id: str) -> Agent:
|
|
47
|
+
return await self.update(agent_id, status="active")
|
|
48
|
+
|
|
49
|
+
async def delete(self, agent_id: str) -> dict[str, Any]:
|
|
50
|
+
return await self._client.delete(f"/v1/agents/{agent_id}")
|
|
51
|
+
|
|
52
|
+
async def unblock(self, agent_id: str) -> dict[str, Any]:
|
|
53
|
+
return await self._client.post(f"/v1/agents/{agent_id}/unblock")
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Async mirror of AuditService."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ..client import AsyncHttpClient
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AsyncAuditService:
|
|
11
|
+
def __init__(self, client: AsyncHttpClient):
|
|
12
|
+
self._client = client
|
|
13
|
+
|
|
14
|
+
async def list(
|
|
15
|
+
self,
|
|
16
|
+
limit: int = 20,
|
|
17
|
+
since: int | None = None,
|
|
18
|
+
agent_id: str | None = None,
|
|
19
|
+
passport_jti: str | None = None,
|
|
20
|
+
) -> dict[str, Any]:
|
|
21
|
+
params: dict[str, str] = {"limit": str(limit)}
|
|
22
|
+
if since is not None:
|
|
23
|
+
params["since"] = str(since)
|
|
24
|
+
if agent_id is not None:
|
|
25
|
+
params["agent_id"] = agent_id
|
|
26
|
+
if passport_jti is not None:
|
|
27
|
+
params["passport_jti"] = passport_jti
|
|
28
|
+
return await self._client.get("/v1/audit", params=params)
|
|
29
|
+
|
|
30
|
+
async def chain_head(self) -> dict[str, Any]:
|
|
31
|
+
return await self._client.get("/v1/audit/chain-head")
|
|
32
|
+
|
|
33
|
+
async def verify_chain(
|
|
34
|
+
self,
|
|
35
|
+
from_date: str | None = None,
|
|
36
|
+
to_date: str | None = None,
|
|
37
|
+
limit: int | None = None,
|
|
38
|
+
) -> dict[str, Any]:
|
|
39
|
+
params: dict[str, str] = {}
|
|
40
|
+
if from_date is not None:
|
|
41
|
+
params["from"] = from_date
|
|
42
|
+
if to_date is not None:
|
|
43
|
+
params["to"] = to_date
|
|
44
|
+
if limit is not None:
|
|
45
|
+
params["limit"] = str(limit)
|
|
46
|
+
return await self._client.get("/v1/audit/verify-chain", params=params)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Async mirror of CredentialService."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from urllib.parse import quote
|
|
6
|
+
|
|
7
|
+
from ..client import AsyncHttpClient
|
|
8
|
+
from ..types import Credential
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AsyncCredentialService:
|
|
12
|
+
def __init__(self, client: AsyncHttpClient):
|
|
13
|
+
self._client = client
|
|
14
|
+
|
|
15
|
+
async def get(self, provider: str, passport_token: str | None = None) -> Credential:
|
|
16
|
+
extra_headers: dict[str, str] = {}
|
|
17
|
+
if passport_token:
|
|
18
|
+
extra_headers["X-Passport-Token"] = passport_token
|
|
19
|
+
data = await self._client.get(
|
|
20
|
+
f"/v1/credentials/{quote(provider)}",
|
|
21
|
+
extra_headers=extra_headers if extra_headers else None,
|
|
22
|
+
)
|
|
23
|
+
return Credential(
|
|
24
|
+
provider=data["provider"],
|
|
25
|
+
credential=data.get("credential"),
|
|
26
|
+
credentials=data.get("credentials"),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
async def get_by_connection(
|
|
30
|
+
self, connection_id: str, passport_token: str | None = None
|
|
31
|
+
) -> Credential:
|
|
32
|
+
extra_headers: dict[str, str] = {}
|
|
33
|
+
if passport_token:
|
|
34
|
+
extra_headers["X-Passport-Token"] = passport_token
|
|
35
|
+
data = await self._client.get(
|
|
36
|
+
f"/v1/credentials/by-connection/{connection_id}",
|
|
37
|
+
extra_headers=extra_headers if extra_headers else None,
|
|
38
|
+
)
|
|
39
|
+
return Credential(
|
|
40
|
+
provider=data["provider"],
|
|
41
|
+
credential=data.get("credential"),
|
|
42
|
+
credentials=data.get("credentials"),
|
|
43
|
+
)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Async mirror of DropoffService."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ..client import AsyncHttpClient
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AsyncDropoffService:
|
|
11
|
+
def __init__(self, client: AsyncHttpClient):
|
|
12
|
+
self._client = client
|
|
13
|
+
|
|
14
|
+
async def create(
|
|
15
|
+
self,
|
|
16
|
+
from_agent_id: str,
|
|
17
|
+
to_agent_id: str,
|
|
18
|
+
schema: dict[str, Any],
|
|
19
|
+
ttl_seconds: int | None = None,
|
|
20
|
+
on_expire: str | None = None,
|
|
21
|
+
) -> dict[str, Any]:
|
|
22
|
+
body: dict[str, Any] = {
|
|
23
|
+
"from_agent": from_agent_id,
|
|
24
|
+
"to_agent": to_agent_id,
|
|
25
|
+
"schema": schema,
|
|
26
|
+
}
|
|
27
|
+
if ttl_seconds:
|
|
28
|
+
body["ttl_seconds"] = ttl_seconds
|
|
29
|
+
if on_expire:
|
|
30
|
+
body["on_expire"] = on_expire
|
|
31
|
+
return await self._client.post("/v1/dropoffs", json=body)
|
|
32
|
+
|
|
33
|
+
async def deposit(self, dropoff_id: str, data: dict[str, Any], agent_id: str) -> dict[str, Any]:
|
|
34
|
+
return await self._client.post(
|
|
35
|
+
f"/v1/dropoffs/{dropoff_id}/deposit",
|
|
36
|
+
json={"agent_id": agent_id, "payload": data},
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
async def collect(self, dropoff_id: str, agent_id: str) -> dict[str, Any]:
|
|
40
|
+
return await self._client.post(
|
|
41
|
+
f"/v1/dropoffs/{dropoff_id}/collect",
|
|
42
|
+
json={"agent_id": agent_id},
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
async def get(self, dropoff_id: str) -> dict[str, Any]:
|
|
46
|
+
return await self._client.get(f"/v1/dropoffs/{dropoff_id}")
|
|
47
|
+
|
|
48
|
+
async def list(self) -> list[dict[str, Any]]:
|
|
49
|
+
return await self._client.get("/v1/dropoffs")
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Async mirror of IdentityService."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ..client import AsyncHttpClient
|
|
8
|
+
from ..types import IdentityProvider, IdentityClaim, IdentitySettings, VerificationSession
|
|
9
|
+
from ..identity import _to_provider, _to_claim
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AsyncIdentityService:
|
|
13
|
+
def __init__(self, client: AsyncHttpClient):
|
|
14
|
+
self._client = client
|
|
15
|
+
|
|
16
|
+
async def get_settings(self) -> IdentitySettings:
|
|
17
|
+
data = await self._client.get("/v1/operators/me/identity-settings")
|
|
18
|
+
return IdentitySettings(
|
|
19
|
+
identity_claim_ttl=data["identity_claim_ttl"],
|
|
20
|
+
identity_claim_inheritance=data["identity_claim_inheritance"],
|
|
21
|
+
identity_auto_revoke=data["identity_auto_revoke"],
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
async def update_settings(
|
|
25
|
+
self,
|
|
26
|
+
*,
|
|
27
|
+
identity_claim_ttl: str | None = None,
|
|
28
|
+
identity_claim_inheritance: str | None = None,
|
|
29
|
+
identity_auto_revoke: bool | None = None,
|
|
30
|
+
) -> IdentitySettings:
|
|
31
|
+
body: dict[str, Any] = {}
|
|
32
|
+
if identity_claim_ttl is not None:
|
|
33
|
+
body["identity_claim_ttl"] = identity_claim_ttl
|
|
34
|
+
if identity_claim_inheritance is not None:
|
|
35
|
+
body["identity_claim_inheritance"] = identity_claim_inheritance
|
|
36
|
+
if identity_auto_revoke is not None:
|
|
37
|
+
body["identity_auto_revoke"] = identity_auto_revoke
|
|
38
|
+
data = await self._client.patch("/v1/operators/me/identity-settings", json=body)
|
|
39
|
+
return IdentitySettings(
|
|
40
|
+
identity_claim_ttl=data["identity_claim_ttl"],
|
|
41
|
+
identity_claim_inheritance=data["identity_claim_inheritance"],
|
|
42
|
+
identity_auto_revoke=data["identity_auto_revoke"],
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
async def list_providers(self) -> list[IdentityProvider]:
|
|
46
|
+
data = await self._client.get("/v1/identity/providers")
|
|
47
|
+
return [_to_provider(p) for p in data]
|
|
48
|
+
|
|
49
|
+
async def initiate_verification(
|
|
50
|
+
self,
|
|
51
|
+
provider_key: str,
|
|
52
|
+
*,
|
|
53
|
+
requested_claims: list[str] | None = None,
|
|
54
|
+
return_url: str | None = None,
|
|
55
|
+
) -> VerificationSession:
|
|
56
|
+
body: dict[str, Any] = {"provider_key": provider_key}
|
|
57
|
+
if requested_claims:
|
|
58
|
+
body["requested_claims"] = requested_claims
|
|
59
|
+
if return_url:
|
|
60
|
+
body["return_url"] = return_url
|
|
61
|
+
data = await self._client.post("/v1/identity/verify/initiate", json=body)
|
|
62
|
+
return VerificationSession(
|
|
63
|
+
session_ref=data["session_ref"],
|
|
64
|
+
authorization_url=data.get("authorization_url"),
|
|
65
|
+
status=data.get("status", "pending"),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
async def complete_verification(self, provider_key: str, session_ref: str) -> dict[str, Any]:
|
|
69
|
+
return await self._client.post(
|
|
70
|
+
"/v1/identity/verify/complete",
|
|
71
|
+
json={"provider_key": provider_key, "session_ref": session_ref},
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
async def grant_delegation(self, delegated_scopes: list[str]) -> IdentityClaim:
|
|
75
|
+
data = await self._client.post(
|
|
76
|
+
"/v1/identity/delegate",
|
|
77
|
+
json={"delegated_scopes": delegated_scopes},
|
|
78
|
+
)
|
|
79
|
+
return _to_claim(data)
|
|
80
|
+
|
|
81
|
+
async def list_claims(self) -> list[IdentityClaim]:
|
|
82
|
+
data = await self._client.get("/v1/identity/claims")
|
|
83
|
+
return [_to_claim(c) for c in data]
|
|
84
|
+
|
|
85
|
+
async def revoke_claim(self, claim_id: str) -> dict[str, Any]:
|
|
86
|
+
return await self._client.delete(f"/v1/identity/claims/{claim_id}")
|
|
87
|
+
|
|
88
|
+
async def get_service_requirements(self, service_id: str) -> dict[str, Any]:
|
|
89
|
+
return await self._client.get(f"/v1/services/{service_id}/requirements")
|
|
90
|
+
|
|
91
|
+
async def set_service_requirement(self, service_id: str, **kwargs: Any) -> dict[str, Any]:
|
|
92
|
+
return await self._client.post(f"/v1/services/{service_id}/requirements", json=kwargs)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Async mirror of IntentsService. asyncio.sleep for the poll loop."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ..client import AsyncHttpClient
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AsyncIntentsService:
|
|
12
|
+
def __init__(self, client: AsyncHttpClient):
|
|
13
|
+
self._client = client
|
|
14
|
+
|
|
15
|
+
async def simulate(
|
|
16
|
+
self,
|
|
17
|
+
intent: dict[str, Any],
|
|
18
|
+
passport_token: str,
|
|
19
|
+
*,
|
|
20
|
+
intent_claim_ref: str | None = None,
|
|
21
|
+
persist: bool | None = None,
|
|
22
|
+
) -> dict[str, Any]:
|
|
23
|
+
body: dict[str, Any] = {"intent": intent}
|
|
24
|
+
if intent_claim_ref is not None:
|
|
25
|
+
body["intent_claim_ref"] = intent_claim_ref
|
|
26
|
+
if persist is not None:
|
|
27
|
+
body["persist"] = persist
|
|
28
|
+
return await self._client.request(
|
|
29
|
+
"POST",
|
|
30
|
+
"/v1/intents/simulate",
|
|
31
|
+
json=body,
|
|
32
|
+
extra_headers={"X-Passport-Token": passport_token},
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
async def submit(
|
|
36
|
+
self,
|
|
37
|
+
intent: dict[str, Any],
|
|
38
|
+
passport_token: str,
|
|
39
|
+
*,
|
|
40
|
+
expires_in_seconds: int | None = None,
|
|
41
|
+
skip_simulation: bool = False,
|
|
42
|
+
) -> dict[str, Any]:
|
|
43
|
+
body: dict[str, Any] = {"intent": intent}
|
|
44
|
+
if expires_in_seconds is not None:
|
|
45
|
+
body["expires_in_seconds"] = expires_in_seconds
|
|
46
|
+
if skip_simulation:
|
|
47
|
+
body["skip_simulation"] = True
|
|
48
|
+
return await self._client.request(
|
|
49
|
+
"POST",
|
|
50
|
+
"/v1/intents/submit",
|
|
51
|
+
json=body,
|
|
52
|
+
extra_headers={"X-Passport-Token": passport_token},
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
async def submit_and_wait(
|
|
56
|
+
self,
|
|
57
|
+
intent: dict[str, Any],
|
|
58
|
+
passport_token: str,
|
|
59
|
+
*,
|
|
60
|
+
expires_in_seconds: int | None = None,
|
|
61
|
+
skip_simulation: bool = False,
|
|
62
|
+
timeout_seconds: float = 60.0,
|
|
63
|
+
poll_interval_seconds: float = 1.0,
|
|
64
|
+
) -> dict[str, Any]:
|
|
65
|
+
"""Async submit + poll. Uses asyncio.sleep instead of time.sleep
|
|
66
|
+
so the event loop stays free during the wait."""
|
|
67
|
+
initial = await self.submit(
|
|
68
|
+
intent,
|
|
69
|
+
passport_token,
|
|
70
|
+
expires_in_seconds=expires_in_seconds,
|
|
71
|
+
skip_simulation=skip_simulation,
|
|
72
|
+
)
|
|
73
|
+
approval_id = initial["approval_id"]
|
|
74
|
+
loop = asyncio.get_event_loop()
|
|
75
|
+
deadline = loop.time() + timeout_seconds
|
|
76
|
+
last_row: dict[str, Any] | None = None
|
|
77
|
+
while loop.time() < deadline:
|
|
78
|
+
row = await self.get(approval_id)
|
|
79
|
+
last_row = row
|
|
80
|
+
if row.get("status") != "pending":
|
|
81
|
+
return {"initial": initial, "final": row, "timed_out": False}
|
|
82
|
+
await asyncio.sleep(poll_interval_seconds)
|
|
83
|
+
return {
|
|
84
|
+
"initial": initial,
|
|
85
|
+
"final": last_row or {"id": approval_id, "status": "pending"},
|
|
86
|
+
"timed_out": True,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async def get(self, approval_id: str) -> dict[str, Any]:
|
|
90
|
+
return await self._client.request("GET", f"/v1/intents/{approval_id}")
|
|
91
|
+
|
|
92
|
+
async def list_pending(
|
|
93
|
+
self, *, page: int | None = None, limit: int | None = None
|
|
94
|
+
) -> dict[str, Any]:
|
|
95
|
+
params: dict[str, Any] = {}
|
|
96
|
+
if page is not None:
|
|
97
|
+
params["page"] = page
|
|
98
|
+
if limit is not None:
|
|
99
|
+
params["limit"] = limit
|
|
100
|
+
return await self._client.request("GET", "/v1/intents/pending", params=params or None)
|
|
101
|
+
|
|
102
|
+
async def approve(self, approval_id: str, *, notes: str | None = None) -> dict[str, Any]:
|
|
103
|
+
body: dict[str, Any] = {}
|
|
104
|
+
if notes is not None:
|
|
105
|
+
body["notes"] = notes
|
|
106
|
+
return await self._client.request(
|
|
107
|
+
"POST",
|
|
108
|
+
f"/v1/intents/{approval_id}/approve",
|
|
109
|
+
json=body,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
async def reject(
|
|
113
|
+
self,
|
|
114
|
+
approval_id: str,
|
|
115
|
+
*,
|
|
116
|
+
notes: str | None = None,
|
|
117
|
+
block_future: bool = False,
|
|
118
|
+
) -> dict[str, Any]:
|
|
119
|
+
body: dict[str, Any] = {}
|
|
120
|
+
if notes is not None:
|
|
121
|
+
body["notes"] = notes
|
|
122
|
+
if block_future:
|
|
123
|
+
body["block_future"] = True
|
|
124
|
+
return await self._client.request(
|
|
125
|
+
"POST",
|
|
126
|
+
f"/v1/intents/{approval_id}/reject",
|
|
127
|
+
json=body,
|
|
128
|
+
)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Async mirror of MissionsService. Same surface, async/await."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ..client import AsyncHttpClient
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AsyncMissionsService:
|
|
11
|
+
def __init__(self, client: AsyncHttpClient):
|
|
12
|
+
self._client = client
|
|
13
|
+
|
|
14
|
+
async def open(
|
|
15
|
+
self,
|
|
16
|
+
intent_summary: str,
|
|
17
|
+
*,
|
|
18
|
+
intent_services: list[str] | None = None,
|
|
19
|
+
expected_counterparties: list[str] | None = None,
|
|
20
|
+
baseline_profile: dict[str, Any] | None = None,
|
|
21
|
+
expected_output_shape: dict[str, Any] | None = None,
|
|
22
|
+
max_actions: int | None = None,
|
|
23
|
+
max_dropoff_bytes: int | None = None,
|
|
24
|
+
coverage_cap_cents: int | None = None,
|
|
25
|
+
max_duration_seconds: int | None = None,
|
|
26
|
+
max_concurrent_intents: int | None = None,
|
|
27
|
+
max_actions_per_service: dict[str, int] | None = None,
|
|
28
|
+
cap_behavior: dict[str, str] | None = None,
|
|
29
|
+
expires_in_seconds: int | None = None,
|
|
30
|
+
parent_passport_services: list[str] | None = None,
|
|
31
|
+
) -> dict[str, Any]:
|
|
32
|
+
body: dict[str, Any] = {"intent_summary": intent_summary}
|
|
33
|
+
if intent_services is not None:
|
|
34
|
+
body["intent_services"] = intent_services
|
|
35
|
+
if expected_counterparties is not None:
|
|
36
|
+
body["expected_counterparties"] = expected_counterparties
|
|
37
|
+
if baseline_profile is not None:
|
|
38
|
+
body["baseline_profile"] = baseline_profile
|
|
39
|
+
if expected_output_shape is not None:
|
|
40
|
+
body["expected_output_shape"] = expected_output_shape
|
|
41
|
+
if max_actions is not None:
|
|
42
|
+
body["max_actions"] = max_actions
|
|
43
|
+
if max_dropoff_bytes is not None:
|
|
44
|
+
body["max_dropoff_bytes"] = max_dropoff_bytes
|
|
45
|
+
if coverage_cap_cents is not None:
|
|
46
|
+
body["coverage_cap_cents"] = coverage_cap_cents
|
|
47
|
+
if max_duration_seconds is not None:
|
|
48
|
+
body["max_duration_seconds"] = max_duration_seconds
|
|
49
|
+
if max_concurrent_intents is not None:
|
|
50
|
+
body["max_concurrent_intents"] = max_concurrent_intents
|
|
51
|
+
if max_actions_per_service is not None:
|
|
52
|
+
body["max_actions_per_service"] = max_actions_per_service
|
|
53
|
+
if cap_behavior is not None:
|
|
54
|
+
body["cap_behavior"] = cap_behavior
|
|
55
|
+
if expires_in_seconds is not None:
|
|
56
|
+
body["expires_in_seconds"] = expires_in_seconds
|
|
57
|
+
if parent_passport_services is not None:
|
|
58
|
+
body["parent_passport_services"] = parent_passport_services
|
|
59
|
+
return await self._client.request("POST", "/v1/missions", json=body)
|
|
60
|
+
|
|
61
|
+
async def complete(
|
|
62
|
+
self,
|
|
63
|
+
mission_id: str,
|
|
64
|
+
*,
|
|
65
|
+
output: dict[str, Any] | None = None,
|
|
66
|
+
) -> dict[str, Any]:
|
|
67
|
+
body: dict[str, Any] = {}
|
|
68
|
+
if output is not None:
|
|
69
|
+
body["output"] = output
|
|
70
|
+
return await self._client.request(
|
|
71
|
+
"POST", f"/v1/missions/{mission_id}/complete", json=body
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
async def revoke(
|
|
75
|
+
self,
|
|
76
|
+
mission_id: str,
|
|
77
|
+
*,
|
|
78
|
+
reason: str,
|
|
79
|
+
notes: str | None = None,
|
|
80
|
+
) -> dict[str, Any]:
|
|
81
|
+
body: dict[str, Any] = {"reason": reason}
|
|
82
|
+
if notes is not None:
|
|
83
|
+
body["notes"] = notes
|
|
84
|
+
return await self._client.request(
|
|
85
|
+
"POST", f"/v1/missions/{mission_id}/revoke", json=body
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
async def get(self, mission_id: str) -> dict[str, Any]:
|
|
89
|
+
return await self._client.request("GET", f"/v1/missions/{mission_id}")
|
|
90
|
+
|
|
91
|
+
async def list(
|
|
92
|
+
self,
|
|
93
|
+
*,
|
|
94
|
+
status: str | None = None,
|
|
95
|
+
page: int | None = None,
|
|
96
|
+
limit: int | None = None,
|
|
97
|
+
) -> dict[str, Any]:
|
|
98
|
+
params: dict[str, Any] = {}
|
|
99
|
+
if status is not None:
|
|
100
|
+
params["status"] = status
|
|
101
|
+
if page is not None:
|
|
102
|
+
params["page"] = page
|
|
103
|
+
if limit is not None:
|
|
104
|
+
params["limit"] = limit
|
|
105
|
+
path = "/v1/missions"
|
|
106
|
+
if params:
|
|
107
|
+
from urllib.parse import urlencode
|
|
108
|
+
|
|
109
|
+
path = f"{path}?{urlencode(params)}"
|
|
110
|
+
return await self._client.request("GET", path)
|