getstack 1.4.0__tar.gz → 3.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.
- {getstack-1.4.0 → getstack-3.0.0}/PKG-INFO +12 -5
- {getstack-1.4.0 → getstack-3.0.0}/README.md +11 -4
- {getstack-1.4.0 → getstack-3.0.0}/pyproject.toml +1 -1
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/__init__.py +21 -6
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/dropoffs.py +10 -2
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/missions.py +23 -3
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/partner_tenants.py +26 -7
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/passport_session.py +14 -7
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/passports.py +169 -59
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/dropoffs.py +20 -4
- getstack-3.0.0/src/getstack/errors.py +145 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/llm.py +2 -1
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/missions.py +77 -13
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/partner_connect.py +3 -2
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/partner_tenants.py +44 -10
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/passport_session.py +19 -9
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/passports.py +297 -68
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/proxy.py +3 -1
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/scan.py +3 -1
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/usage.py +4 -0
- getstack-3.0.0/tests/test_dropoffs.py +117 -0
- getstack-3.0.0/tests/test_mission_binding.py +339 -0
- {getstack-1.4.0 → getstack-3.0.0}/tests/test_parity.py +52 -4
- getstack-3.0.0/tests/test_partner_signing.py +69 -0
- getstack-1.4.0/src/getstack/errors.py +0 -79
- getstack-1.4.0/tests/test_dropoffs.py +0 -70
- getstack-1.4.0/tests/test_partner_signing.py +0 -36
- {getstack-1.4.0 → getstack-3.0.0}/.gitignore +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/CLAUDE.md +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/LICENSE +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/scripts/verify_sign_claim_crosslang.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/__init__.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/agents.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/audit.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/authority.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/credentials.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/delivery_methods.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/detector_config.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/evidence_packs.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/fleets.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/identity.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/inbound_webhooks.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/intents.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/llm.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/notifications.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/partner_connect.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/pii_config.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/proxy.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/reviews.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/scan.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/security_events.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/services.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/skills.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/team.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_async/usage.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_authority_request.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/_fleet_key.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/agent_auth.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/agents.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/audit.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/auth.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/authority.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/browser_bootstrap.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/client.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/credentials.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/delivery_methods.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/detector_config.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/evidence_packs.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/fleets.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/identity.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/inbound_webhooks.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/intents.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/notifications.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/passport_header_verify.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/pii_config.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/py.typed +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/reviews.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/security_events.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/services.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/sign_claim.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/skills.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/team.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/types.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/src/getstack/verify_offline.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/tests/test_authority.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/tests/test_delivery_methods.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/tests/test_errors.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/tests/test_evidence_packs.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/tests/test_fleets.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/tests/test_identity_requirements.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/tests/test_inbound_webhooks.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/tests/test_missions.py +0 -0
- {getstack-1.4.0 → getstack-3.0.0}/tests/test_sign_claim.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: getstack
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0
|
|
4
4
|
Summary: Python SDK for STACK — trust infrastructure for AI agents
|
|
5
5
|
Project-URL: Homepage, https://getstack.run
|
|
6
6
|
Project-URL: Documentation, https://getstack.run/docs/sdk
|
|
@@ -125,8 +125,15 @@ stack.agents.list()
|
|
|
125
125
|
stack.agents.update(agent_id, **fields)
|
|
126
126
|
stack.agents.unblock(agent_id)
|
|
127
127
|
|
|
128
|
+
# Missions (every passport is issued for exactly one Mission)
|
|
129
|
+
stack.missions.open(intent_summary, agent_id=None, intent_services=None, ...)
|
|
130
|
+
stack.missions.start(agent_id=agent_id, mission={"intent_summary": "..."}, passport=None)
|
|
131
|
+
stack.missions.complete(mission_id) # also revokes the Mission's passports
|
|
132
|
+
stack.missions.revoke(mission_id, reason="operator_initiated_clean")
|
|
133
|
+
|
|
128
134
|
# Passports
|
|
129
|
-
stack.passports.issue(agent_id, intent=None, services=None, ...)
|
|
135
|
+
stack.passports.issue(agent_id, mission_ids=[mission_id], intent=None, services=None, ...)
|
|
136
|
+
stack.passports.mission(agent_id, intent, services, ...) # context manager: Mission + passport, closed on exit
|
|
130
137
|
stack.passports.verify(token)
|
|
131
138
|
stack.passports.revoke(jti, reason=None)
|
|
132
139
|
stack.passports.checkpoint(jti, services_used, actions_count, ...)
|
|
@@ -148,9 +155,9 @@ stack.reviews.list(status="flagged")
|
|
|
148
155
|
stack.reviews.decide(checkout_id, decision, notes=None, block_future=False)
|
|
149
156
|
|
|
150
157
|
# Drop-offs
|
|
151
|
-
stack.dropoffs.create(from_agent_id, to_agent_id, schema,
|
|
152
|
-
stack.dropoffs.deposit(dropoff_id, data, agent_id)
|
|
153
|
-
stack.dropoffs.collect(dropoff_id, agent_id)
|
|
158
|
+
stack.dropoffs.create(from_agent_id, to_agent_id, schema, ..., passport_token=None)
|
|
159
|
+
stack.dropoffs.deposit(dropoff_id, data, agent_id, passport_token=None)
|
|
160
|
+
stack.dropoffs.collect(dropoff_id, agent_id, passport_token=None)
|
|
154
161
|
|
|
155
162
|
# Notifications
|
|
156
163
|
stack.notifications.list()
|
|
@@ -98,8 +98,15 @@ stack.agents.list()
|
|
|
98
98
|
stack.agents.update(agent_id, **fields)
|
|
99
99
|
stack.agents.unblock(agent_id)
|
|
100
100
|
|
|
101
|
+
# Missions (every passport is issued for exactly one Mission)
|
|
102
|
+
stack.missions.open(intent_summary, agent_id=None, intent_services=None, ...)
|
|
103
|
+
stack.missions.start(agent_id=agent_id, mission={"intent_summary": "..."}, passport=None)
|
|
104
|
+
stack.missions.complete(mission_id) # also revokes the Mission's passports
|
|
105
|
+
stack.missions.revoke(mission_id, reason="operator_initiated_clean")
|
|
106
|
+
|
|
101
107
|
# Passports
|
|
102
|
-
stack.passports.issue(agent_id, intent=None, services=None, ...)
|
|
108
|
+
stack.passports.issue(agent_id, mission_ids=[mission_id], intent=None, services=None, ...)
|
|
109
|
+
stack.passports.mission(agent_id, intent, services, ...) # context manager: Mission + passport, closed on exit
|
|
103
110
|
stack.passports.verify(token)
|
|
104
111
|
stack.passports.revoke(jti, reason=None)
|
|
105
112
|
stack.passports.checkpoint(jti, services_used, actions_count, ...)
|
|
@@ -121,9 +128,9 @@ stack.reviews.list(status="flagged")
|
|
|
121
128
|
stack.reviews.decide(checkout_id, decision, notes=None, block_future=False)
|
|
122
129
|
|
|
123
130
|
# Drop-offs
|
|
124
|
-
stack.dropoffs.create(from_agent_id, to_agent_id, schema,
|
|
125
|
-
stack.dropoffs.deposit(dropoff_id, data, agent_id)
|
|
126
|
-
stack.dropoffs.collect(dropoff_id, agent_id)
|
|
131
|
+
stack.dropoffs.create(from_agent_id, to_agent_id, schema, ..., passport_token=None)
|
|
132
|
+
stack.dropoffs.deposit(dropoff_id, data, agent_id, passport_token=None)
|
|
133
|
+
stack.dropoffs.collect(dropoff_id, agent_id, passport_token=None)
|
|
127
134
|
|
|
128
135
|
# Notifications
|
|
129
136
|
stack.notifications.list()
|
|
@@ -52,7 +52,7 @@ from .auth import ApiKeyAuth, SessionAuth, OAuthAuth, CredentialsFileAuth
|
|
|
52
52
|
from .agent_auth import AgentKeypairAuth
|
|
53
53
|
from .client import HttpClient, AsyncHttpClient, DEFAULT_BASE_URL
|
|
54
54
|
from .agents import AgentService
|
|
55
|
-
from .passports import PassportService, Mission
|
|
55
|
+
from .passports import PassportService, Mission, _require_one_mission
|
|
56
56
|
from .credentials import CredentialService
|
|
57
57
|
from .services import ServiceService
|
|
58
58
|
from .dropoffs import DropoffService
|
|
@@ -119,6 +119,7 @@ from .errors import (
|
|
|
119
119
|
ForbiddenError,
|
|
120
120
|
ValidationError,
|
|
121
121
|
PassportBlockedError,
|
|
122
|
+
MissionEndedError,
|
|
122
123
|
)
|
|
123
124
|
|
|
124
125
|
try:
|
|
@@ -222,6 +223,7 @@ __all__ = [
|
|
|
222
223
|
"ForbiddenError",
|
|
223
224
|
"ValidationError",
|
|
224
225
|
"PassportBlockedError",
|
|
226
|
+
"MissionEndedError",
|
|
225
227
|
]
|
|
226
228
|
|
|
227
229
|
|
|
@@ -301,18 +303,25 @@ class Stack:
|
|
|
301
303
|
ttl_seconds: int | None = None,
|
|
302
304
|
accountability_mode: str | None = None,
|
|
303
305
|
authority_binding_ids: list[str] | None = None,
|
|
306
|
+
mission_ids: list[str] | None = None,
|
|
304
307
|
**kwargs: Any,
|
|
305
308
|
) -> PassportSession:
|
|
306
309
|
"""Phase 5.2.6 — issue a passport and return a PassportSession
|
|
307
310
|
that holds the token + auto-refreshes ~60s before expiry. Pass
|
|
308
311
|
the returned session to LLM calls instead of threading a raw
|
|
309
|
-
token per call
|
|
312
|
+
token per call. ``mission_ids`` must hold exactly one Mission id;
|
|
313
|
+
once that Mission ends, refresh raises ``MissionEndedError``::
|
|
310
314
|
|
|
311
|
-
|
|
315
|
+
mission = stack.missions.open("Answer support tickets", agent_id="agt_x")
|
|
316
|
+
session = stack.issue_passport(agent_id="agt_x", mission_ids=[mission["mission_id"]])
|
|
312
317
|
for i in range(1000):
|
|
313
318
|
stack.llm.anthropic.messages.create(body, passport=session)
|
|
314
319
|
"""
|
|
315
|
-
issue_input: dict[str, Any] = {
|
|
320
|
+
issue_input: dict[str, Any] = {
|
|
321
|
+
"agent_id": agent_id,
|
|
322
|
+
**kwargs,
|
|
323
|
+
"mission_ids": _require_one_mission(mission_ids),
|
|
324
|
+
}
|
|
316
325
|
if services is not None: issue_input["services"] = services
|
|
317
326
|
if intents is not None: issue_input["intents"] = intents
|
|
318
327
|
if ttl_seconds is not None: issue_input["ttl_seconds"] = ttl_seconds
|
|
@@ -483,10 +492,16 @@ class AsyncStack:
|
|
|
483
492
|
ttl_seconds: int | None = None,
|
|
484
493
|
accountability_mode: str | None = None,
|
|
485
494
|
authority_binding_ids: list[str] | None = None,
|
|
495
|
+
mission_ids: list[str] | None = None,
|
|
486
496
|
**kwargs: Any,
|
|
487
497
|
):
|
|
488
|
-
"""Phase 5.2.6 — async passport issue + session wrapper.
|
|
489
|
-
|
|
498
|
+
"""Phase 5.2.6 — async passport issue + session wrapper.
|
|
499
|
+
``mission_ids`` must hold exactly one Mission id."""
|
|
500
|
+
issue_input: dict[str, Any] = {
|
|
501
|
+
"agent_id": agent_id,
|
|
502
|
+
**kwargs,
|
|
503
|
+
"mission_ids": _require_one_mission(mission_ids),
|
|
504
|
+
}
|
|
490
505
|
if services is not None: issue_input["services"] = services
|
|
491
506
|
if intents is not None: issue_input["intents"] = intents
|
|
492
507
|
if ttl_seconds is not None: issue_input["ttl_seconds"] = ttl_seconds
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
7
|
from ..client import AsyncHttpClient
|
|
8
|
+
from ..dropoffs import _passport_header
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class AsyncDropoffService:
|
|
@@ -18,7 +19,9 @@ class AsyncDropoffService:
|
|
|
18
19
|
schema: dict[str, Any],
|
|
19
20
|
ttl_seconds: int | None = None,
|
|
20
21
|
on_expire: str | None = None,
|
|
22
|
+
passport_token: str | None = None,
|
|
21
23
|
) -> dict[str, Any]:
|
|
24
|
+
"""Create a drop-off. An agent caller passes the producer's Mission Passport as passport_token."""
|
|
22
25
|
body: dict[str, Any] = {
|
|
23
26
|
"from_agent": from_agent_id,
|
|
24
27
|
"to_agent": to_agent_id,
|
|
@@ -28,7 +31,7 @@ class AsyncDropoffService:
|
|
|
28
31
|
body["ttl_seconds"] = ttl_seconds
|
|
29
32
|
if on_expire:
|
|
30
33
|
body["on_expire"] = on_expire
|
|
31
|
-
return await self._client.post("/v1/dropoffs", json=body)
|
|
34
|
+
return await self._client.post("/v1/dropoffs", json=body, extra_headers=_passport_header(passport_token))
|
|
32
35
|
|
|
33
36
|
async def deposit(
|
|
34
37
|
self,
|
|
@@ -37,7 +40,9 @@ class AsyncDropoffService:
|
|
|
37
40
|
agent_id: str,
|
|
38
41
|
passport_jti: str | None = None,
|
|
39
42
|
mission_id: str | None = None,
|
|
43
|
+
passport_token: str | None = None,
|
|
40
44
|
) -> dict[str, Any]:
|
|
45
|
+
"""Deposit as the producer. An agent caller passes its Mission Passport as passport_token (or passport_jti)."""
|
|
41
46
|
body: dict[str, Any] = {"agent_id": agent_id, "payload": data}
|
|
42
47
|
if passport_jti is not None:
|
|
43
48
|
body["passport_jti"] = passport_jti
|
|
@@ -46,12 +51,15 @@ class AsyncDropoffService:
|
|
|
46
51
|
return await self._client.post(
|
|
47
52
|
f"/v1/dropoffs/{dropoff_id}/deposit",
|
|
48
53
|
json=body,
|
|
54
|
+
extra_headers=_passport_header(passport_token),
|
|
49
55
|
)
|
|
50
56
|
|
|
51
|
-
async def collect(self, dropoff_id: str, agent_id: str) -> dict[str, Any]:
|
|
57
|
+
async def collect(self, dropoff_id: str, agent_id: str, passport_token: str | None = None) -> dict[str, Any]:
|
|
58
|
+
"""Collect as the consumer. An agent caller passes its Mission Passport as passport_token."""
|
|
52
59
|
return await self._client.post(
|
|
53
60
|
f"/v1/dropoffs/{dropoff_id}/collect",
|
|
54
61
|
json={"agent_id": agent_id},
|
|
62
|
+
extra_headers=_passport_header(passport_token),
|
|
55
63
|
)
|
|
56
64
|
|
|
57
65
|
async def expire(self, dropoff_id: str) -> dict[str, Any]:
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
"""Async mirror of MissionsService. Same surface, async/await.
|
|
1
|
+
"""Async mirror of MissionsService. Same surface, async/await.
|
|
2
|
+
|
|
3
|
+
Completing or revoking a Mission (any reason) revokes every passport
|
|
4
|
+
bound to it.
|
|
5
|
+
"""
|
|
2
6
|
|
|
3
7
|
from __future__ import annotations
|
|
4
8
|
|
|
@@ -6,14 +10,26 @@ from typing import Any
|
|
|
6
10
|
from urllib.parse import quote
|
|
7
11
|
|
|
8
12
|
from ..client import AsyncHttpClient
|
|
13
|
+
from ..missions import _start_body
|
|
9
14
|
|
|
10
15
|
|
|
11
16
|
class AsyncMissionsService:
|
|
12
17
|
def __init__(self, client: AsyncHttpClient):
|
|
13
18
|
self._client = client
|
|
14
19
|
|
|
15
|
-
async def start(
|
|
16
|
-
|
|
20
|
+
async def start(
|
|
21
|
+
self,
|
|
22
|
+
request: dict[str, Any] | None = None,
|
|
23
|
+
*,
|
|
24
|
+
agent_id: str | None = None,
|
|
25
|
+
mission: dict[str, Any] | None = None,
|
|
26
|
+
passport: dict[str, Any] | None = None,
|
|
27
|
+
) -> dict[str, Any]:
|
|
28
|
+
"""Open a Mission and issue its passport in one call. Operator form:
|
|
29
|
+
``await stack.missions.start(agent_id=..., mission={...}, passport={...})``.
|
|
30
|
+
Fleet form (unchanged): ``await stack.missions.start({"fleet_policy_id": ...})``."""
|
|
31
|
+
body = _start_body(request, agent_id, mission, passport)
|
|
32
|
+
return await self._client.request("POST", "/v1/missions/start", json=body)
|
|
17
33
|
|
|
18
34
|
async def renew(self, mission_id: str) -> dict[str, Any]:
|
|
19
35
|
return await self._client.request("POST", f"/v1/missions/{quote(mission_id, safe='')}/renew", json={})
|
|
@@ -41,8 +57,12 @@ class AsyncMissionsService:
|
|
|
41
57
|
requested_operations: list[dict[str, Any]] | None = None,
|
|
42
58
|
limits: dict[str, Any] | None = None,
|
|
43
59
|
authority_binding_ids: list[str] | None = None,
|
|
60
|
+
agent_id: str | None = None,
|
|
44
61
|
) -> dict[str, Any]:
|
|
62
|
+
"""Open a new mission. ``agent_id`` assigns it to that agent."""
|
|
45
63
|
body: dict[str, Any] = {"intent_summary": intent_summary}
|
|
64
|
+
if agent_id is not None:
|
|
65
|
+
body["agent_id"] = agent_id
|
|
46
66
|
if intent_services is not None:
|
|
47
67
|
body["intent_services"] = intent_services
|
|
48
68
|
if expected_counterparties is not None:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Async mirror of PartnerTenantsService (
|
|
1
|
+
"""Async mirror of PartnerTenantsService (white-label tenants)."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -6,10 +6,9 @@ from typing import Any, Literal, Optional
|
|
|
6
6
|
from urllib.parse import quote
|
|
7
7
|
|
|
8
8
|
from ..client import AsyncHttpClient
|
|
9
|
+
from ..passports import _require_one_mission
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
Tier = Literal["free", "developer", "pro", "business", "enterprise"]
|
|
12
|
-
|
|
13
12
|
|
|
14
13
|
class AsyncPartnerTenantsService:
|
|
15
14
|
def __init__(self, client: AsyncHttpClient):
|
|
@@ -18,23 +17,36 @@ class AsyncPartnerTenantsService:
|
|
|
18
17
|
async def create(
|
|
19
18
|
self,
|
|
20
19
|
external_customer_id: Optional[str] = None,
|
|
21
|
-
tier: Optional[Tier] = None,
|
|
22
20
|
) -> dict[str, Any]:
|
|
21
|
+
"""Create a white-label tenant.
|
|
22
|
+
|
|
23
|
+
Tenants have no plan of their own; they draw from your allowance
|
|
24
|
+
and wallet. Returns operator_id, api_key (shown once — store it),
|
|
25
|
+
external_customer_id, tier ('partner'), billing ('partner') and
|
|
26
|
+
created_at. Raises with code TENANT_LIMIT_REACHED (409) when your
|
|
27
|
+
deal caps the tenant count.
|
|
28
|
+
"""
|
|
23
29
|
body: dict[str, Any] = {}
|
|
24
30
|
if external_customer_id is not None:
|
|
25
31
|
body["external_customer_id"] = external_customer_id
|
|
26
|
-
if tier is not None:
|
|
27
|
-
body["tier"] = tier
|
|
28
32
|
return await self._client.post("/v1/partner/tenants", json=body)
|
|
29
33
|
|
|
30
34
|
async def list(self) -> list[dict[str, Any]]:
|
|
35
|
+
"""List your tenants. Each carries ``usage`` ({period, actions}):
|
|
36
|
+
its month-to-date share of your action pool, when available."""
|
|
31
37
|
res = await self._client.get("/v1/partner/tenants")
|
|
32
38
|
return list(res.get("tenants", []))
|
|
33
39
|
|
|
34
40
|
async def get(self, tenant_id: str) -> dict[str, Any]:
|
|
41
|
+
"""Fetch one tenant (same shape as a ``list`` entry)."""
|
|
35
42
|
return await self._client.get(f"/v1/partner/tenants/{tenant_id}")
|
|
36
43
|
|
|
37
44
|
async def revoke(self, tenant_id: str) -> dict[str, Any]:
|
|
45
|
+
"""Revoke a tenant. Cascades to its inbound webhooks and passports.
|
|
46
|
+
|
|
47
|
+
Returns {transitioned, webhooks_revoked, passports_revoked}.
|
|
48
|
+
Idempotent: a retry finishes a cascade that failed part-way.
|
|
49
|
+
"""
|
|
38
50
|
return await self._client.delete(f"/v1/partner/tenants/{tenant_id}")
|
|
39
51
|
|
|
40
52
|
async def create_inbound_webhook(
|
|
@@ -76,8 +88,12 @@ class AsyncPartnerTenantsService:
|
|
|
76
88
|
services: Optional[list[dict[str, Any]]] = None,
|
|
77
89
|
named_intents: Optional[list[str]] = None,
|
|
78
90
|
accountability_mode: Optional[Literal["enforced", "logged", "standard"]] = None,
|
|
91
|
+
mission_ids: Optional[list[str]] = None,
|
|
79
92
|
) -> dict[str, Any]:
|
|
80
|
-
|
|
93
|
+
"""Issue a passport under the tenant. ``mission_ids`` must hold exactly
|
|
94
|
+
one Mission id the tenant's agent may use; anything else raises
|
|
95
|
+
``ValueError`` before any request."""
|
|
96
|
+
body: dict[str, Any] = {"agent_id": agent_id, "mission_ids": _require_one_mission(mission_ids)}
|
|
81
97
|
if ttl_seconds is not None:
|
|
82
98
|
body["ttl_seconds"] = ttl_seconds
|
|
83
99
|
if services is not None:
|
|
@@ -99,6 +115,8 @@ class AsyncPartnerTenantsService:
|
|
|
99
115
|
ttl_seconds: int = 900,
|
|
100
116
|
accountability_mode: Literal["enforced", "logged", "standard"] = "enforced",
|
|
101
117
|
named_intents: Optional[list[str]] = None,
|
|
118
|
+
*,
|
|
119
|
+
mission_id: str,
|
|
102
120
|
) -> dict[str, Any]:
|
|
103
121
|
"""Phase 5.7 — convenience helper for the MIME-stamping pattern.
|
|
104
122
|
|
|
@@ -112,6 +130,7 @@ class AsyncPartnerTenantsService:
|
|
|
112
130
|
accountability_mode=accountability_mode,
|
|
113
131
|
services=services,
|
|
114
132
|
named_intents=named_intents,
|
|
133
|
+
mission_ids=[mission_id],
|
|
115
134
|
)
|
|
116
135
|
token = issued.get("token") or issued.get("passport")
|
|
117
136
|
passport_id = issued.get("passport_id") or issued.get("jti")
|
|
@@ -6,6 +6,7 @@ import time
|
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
8
|
from ..client import AsyncHttpClient
|
|
9
|
+
from ..errors import StackError, mission_ended_error
|
|
9
10
|
from ..passport_session import _decode_jwt_exp
|
|
10
11
|
|
|
11
12
|
|
|
@@ -25,13 +26,19 @@ class AsyncPassportSession:
|
|
|
25
26
|
now_sec = int(time.time())
|
|
26
27
|
if self._exp_seconds - now_sec > grace_seconds:
|
|
27
28
|
return self._current_token
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
try:
|
|
30
|
+
refreshed = await self._client.post(
|
|
31
|
+
"/v1/passports/refresh",
|
|
32
|
+
json={
|
|
33
|
+
"token": self._current_token,
|
|
34
|
+
"ttl_seconds": self._issue_input.get("ttl_seconds"),
|
|
35
|
+
},
|
|
36
|
+
)
|
|
37
|
+
except StackError as exc:
|
|
38
|
+
ended = mission_ended_error(exc)
|
|
39
|
+
if ended is not None:
|
|
40
|
+
raise ended from exc
|
|
41
|
+
raise
|
|
35
42
|
self._current_token = refreshed["token"]
|
|
36
43
|
self._exp_seconds = _decode_jwt_exp(self._current_token)
|
|
37
44
|
return self._current_token
|