settld-api-sdk-python 0.1.0__tar.gz → 0.1.2__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.
- settld_api_sdk_python-0.1.2/PKG-INFO +20 -0
- settld_api_sdk_python-0.1.2/README.md +11 -0
- {settld_api_sdk_python-0.1.0 → settld_api_sdk_python-0.1.2}/pyproject.toml +1 -1
- settld_api_sdk_python-0.1.2/settld_api_sdk/client.py +691 -0
- settld_api_sdk_python-0.1.2/settld_api_sdk_python.egg-info/PKG-INFO +20 -0
- settld_api_sdk_python-0.1.0/PKG-INFO +0 -14
- settld_api_sdk_python-0.1.0/README.md +0 -5
- settld_api_sdk_python-0.1.0/settld_api_sdk/client.py +0 -346
- settld_api_sdk_python-0.1.0/settld_api_sdk_python.egg-info/PKG-INFO +0 -14
- {settld_api_sdk_python-0.1.0 → settld_api_sdk_python-0.1.2}/settld_api_sdk/__init__.py +0 -0
- {settld_api_sdk_python-0.1.0 → settld_api_sdk_python-0.1.2}/settld_api_sdk_python.egg-info/SOURCES.txt +0 -0
- {settld_api_sdk_python-0.1.0 → settld_api_sdk_python-0.1.2}/settld_api_sdk_python.egg-info/dependency_links.txt +0 -0
- {settld_api_sdk_python-0.1.0 → settld_api_sdk_python-0.1.2}/settld_api_sdk_python.egg-info/top_level.txt +0 -0
- {settld_api_sdk_python-0.1.0 → settld_api_sdk_python-0.1.2}/setup.cfg +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: settld-api-sdk-python
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Settld API SDK (Python)
|
|
5
|
+
Author: Settld
|
|
6
|
+
License: UNLICENSED
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# Settld API SDK (Python)
|
|
11
|
+
|
|
12
|
+
Python client for Settld API endpoints, including high-level helpers:
|
|
13
|
+
- `first_verified_run` (register agents, run work, verify, settle)
|
|
14
|
+
- `first_paid_rfq` (rfq -> bid -> accept -> run -> settlement)
|
|
15
|
+
- run settlement/dispute lifecycle: `get_run_settlement_policy_replay`, `resolve_run_settlement`, `open_run_dispute`, `submit_run_dispute_evidence`, `escalate_run_dispute`, `close_run_dispute`
|
|
16
|
+
- `get_tenant_analytics` / `get_tenant_trust_graph`
|
|
17
|
+
- `list_tenant_trust_graph_snapshots` / `create_tenant_trust_graph_snapshot` / `diff_tenant_trust_graph`
|
|
18
|
+
- auth headers: `api_key` (Bearer) and optional `x_api_key` (Magic Link)
|
|
19
|
+
|
|
20
|
+
Quickstart docs live in `docs/QUICKSTART_SDK_PYTHON.md` at repo root.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Settld API SDK (Python)
|
|
2
|
+
|
|
3
|
+
Python client for Settld API endpoints, including high-level helpers:
|
|
4
|
+
- `first_verified_run` (register agents, run work, verify, settle)
|
|
5
|
+
- `first_paid_rfq` (rfq -> bid -> accept -> run -> settlement)
|
|
6
|
+
- run settlement/dispute lifecycle: `get_run_settlement_policy_replay`, `resolve_run_settlement`, `open_run_dispute`, `submit_run_dispute_evidence`, `escalate_run_dispute`, `close_run_dispute`
|
|
7
|
+
- `get_tenant_analytics` / `get_tenant_trust_graph`
|
|
8
|
+
- `list_tenant_trust_graph_snapshots` / `create_tenant_trust_graph_snapshot` / `diff_tenant_trust_graph`
|
|
9
|
+
- auth headers: `api_key` (Bearer) and optional `x_api_key` (Magic Link)
|
|
10
|
+
|
|
11
|
+
Quickstart docs live in `docs/QUICKSTART_SDK_PYTHON.md` at repo root.
|
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import random
|
|
5
|
+
import time
|
|
6
|
+
import uuid
|
|
7
|
+
from typing import Any, Dict, Optional
|
|
8
|
+
from urllib import error, parse, request
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _assert_non_empty_string(value: Any, name: str) -> str:
|
|
12
|
+
if not isinstance(value, str) or value.strip() == "":
|
|
13
|
+
raise ValueError(f"{name} must be a non-empty string")
|
|
14
|
+
return value
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _random_request_id() -> str:
|
|
18
|
+
return f"req_{uuid.uuid4().hex}"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _normalize_prefix(value: Optional[str], fallback: str) -> str:
|
|
22
|
+
if isinstance(value, str) and value.strip():
|
|
23
|
+
return value.strip()
|
|
24
|
+
return fallback
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SettldApiError(Exception):
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
*,
|
|
31
|
+
status: int,
|
|
32
|
+
message: str,
|
|
33
|
+
code: Optional[str] = None,
|
|
34
|
+
details: Any = None,
|
|
35
|
+
request_id: Optional[str] = None,
|
|
36
|
+
) -> None:
|
|
37
|
+
super().__init__(message)
|
|
38
|
+
self.status = status
|
|
39
|
+
self.code = code
|
|
40
|
+
self.details = details
|
|
41
|
+
self.request_id = request_id
|
|
42
|
+
|
|
43
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
44
|
+
return {
|
|
45
|
+
"status": self.status,
|
|
46
|
+
"code": self.code,
|
|
47
|
+
"message": str(self),
|
|
48
|
+
"details": self.details,
|
|
49
|
+
"requestId": self.request_id,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class SettldClient:
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
*,
|
|
57
|
+
base_url: str,
|
|
58
|
+
tenant_id: str,
|
|
59
|
+
protocol: str = "1.0",
|
|
60
|
+
api_key: Optional[str] = None,
|
|
61
|
+
x_api_key: Optional[str] = None,
|
|
62
|
+
user_agent: Optional[str] = None,
|
|
63
|
+
timeout_seconds: float = 30.0,
|
|
64
|
+
) -> None:
|
|
65
|
+
self.base_url = _assert_non_empty_string(base_url, "base_url").rstrip("/")
|
|
66
|
+
self.tenant_id = _assert_non_empty_string(tenant_id, "tenant_id")
|
|
67
|
+
self.protocol = protocol
|
|
68
|
+
self.api_key = api_key
|
|
69
|
+
self.x_api_key = x_api_key
|
|
70
|
+
self.user_agent = user_agent
|
|
71
|
+
self.timeout_seconds = timeout_seconds
|
|
72
|
+
|
|
73
|
+
def _request(
|
|
74
|
+
self,
|
|
75
|
+
method: str,
|
|
76
|
+
path: str,
|
|
77
|
+
*,
|
|
78
|
+
body: Optional[Dict[str, Any]] = None,
|
|
79
|
+
request_id: Optional[str] = None,
|
|
80
|
+
idempotency_key: Optional[str] = None,
|
|
81
|
+
expected_prev_chain_hash: Optional[str] = None,
|
|
82
|
+
timeout_seconds: Optional[float] = None,
|
|
83
|
+
) -> Dict[str, Any]:
|
|
84
|
+
rid = request_id if request_id else _random_request_id()
|
|
85
|
+
headers = {
|
|
86
|
+
"content-type": "application/json",
|
|
87
|
+
"x-proxy-tenant-id": self.tenant_id,
|
|
88
|
+
"x-settld-protocol": self.protocol,
|
|
89
|
+
"x-request-id": rid,
|
|
90
|
+
}
|
|
91
|
+
if self.user_agent:
|
|
92
|
+
headers["user-agent"] = self.user_agent
|
|
93
|
+
if self.api_key:
|
|
94
|
+
headers["authorization"] = f"Bearer {self.api_key}"
|
|
95
|
+
if self.x_api_key:
|
|
96
|
+
headers["x-api-key"] = str(self.x_api_key)
|
|
97
|
+
if idempotency_key:
|
|
98
|
+
headers["x-idempotency-key"] = str(idempotency_key)
|
|
99
|
+
if expected_prev_chain_hash:
|
|
100
|
+
headers["x-proxy-expected-prev-chain-hash"] = str(expected_prev_chain_hash)
|
|
101
|
+
|
|
102
|
+
url = parse.urljoin(f"{self.base_url}/", path.lstrip("/"))
|
|
103
|
+
payload = None if body is None else json.dumps(body).encode("utf-8")
|
|
104
|
+
req = request.Request(url=url, data=payload, method=method, headers=headers)
|
|
105
|
+
timeout = self.timeout_seconds if timeout_seconds is None else timeout_seconds
|
|
106
|
+
try:
|
|
107
|
+
with request.urlopen(req, timeout=timeout) as response:
|
|
108
|
+
raw = response.read().decode("utf-8")
|
|
109
|
+
parsed = None
|
|
110
|
+
if raw:
|
|
111
|
+
try:
|
|
112
|
+
parsed = json.loads(raw)
|
|
113
|
+
except json.JSONDecodeError:
|
|
114
|
+
parsed = {"raw": raw}
|
|
115
|
+
response_headers = {str(k).lower(): str(v) for k, v in response.headers.items()}
|
|
116
|
+
return {
|
|
117
|
+
"ok": True,
|
|
118
|
+
"status": int(response.status),
|
|
119
|
+
"requestId": response_headers.get("x-request-id"),
|
|
120
|
+
"body": parsed,
|
|
121
|
+
"headers": response_headers,
|
|
122
|
+
}
|
|
123
|
+
except error.HTTPError as http_error:
|
|
124
|
+
raw = http_error.read().decode("utf-8")
|
|
125
|
+
parsed: Any = {}
|
|
126
|
+
if raw:
|
|
127
|
+
try:
|
|
128
|
+
parsed = json.loads(raw)
|
|
129
|
+
except json.JSONDecodeError:
|
|
130
|
+
parsed = {"raw": raw}
|
|
131
|
+
response_headers = {str(k).lower(): str(v) for k, v in http_error.headers.items()}
|
|
132
|
+
raise SettldApiError(
|
|
133
|
+
status=int(http_error.code),
|
|
134
|
+
code=parsed.get("code") if isinstance(parsed, dict) else None,
|
|
135
|
+
message=parsed.get("error", f"request failed ({http_error.code})") if isinstance(parsed, dict) else f"request failed ({http_error.code})",
|
|
136
|
+
details=parsed.get("details") if isinstance(parsed, dict) else None,
|
|
137
|
+
request_id=response_headers.get("x-request-id"),
|
|
138
|
+
) from http_error
|
|
139
|
+
|
|
140
|
+
def register_agent(self, body: Dict[str, Any], **opts: Any) -> Dict[str, Any]:
|
|
141
|
+
if not isinstance(body, dict):
|
|
142
|
+
raise ValueError("body is required")
|
|
143
|
+
_assert_non_empty_string(body.get("publicKeyPem"), "body.publicKeyPem")
|
|
144
|
+
return self._request("POST", "/agents/register", body=body, **opts)
|
|
145
|
+
|
|
146
|
+
def credit_agent_wallet(self, agent_id: str, body: Dict[str, Any], **opts: Any) -> Dict[str, Any]:
|
|
147
|
+
_assert_non_empty_string(agent_id, "agent_id")
|
|
148
|
+
if not isinstance(body, dict):
|
|
149
|
+
raise ValueError("body is required")
|
|
150
|
+
return self._request("POST", f"/agents/{parse.quote(agent_id, safe='')}/wallet/credit", body=body, **opts)
|
|
151
|
+
|
|
152
|
+
def get_agent_wallet(self, agent_id: str, **opts: Any) -> Dict[str, Any]:
|
|
153
|
+
_assert_non_empty_string(agent_id, "agent_id")
|
|
154
|
+
return self._request("GET", f"/agents/{parse.quote(agent_id, safe='')}/wallet", **opts)
|
|
155
|
+
|
|
156
|
+
def create_agent_run(self, agent_id: str, body: Optional[Dict[str, Any]] = None, **opts: Any) -> Dict[str, Any]:
|
|
157
|
+
_assert_non_empty_string(agent_id, "agent_id")
|
|
158
|
+
run_body = {} if body is None else body
|
|
159
|
+
if not isinstance(run_body, dict):
|
|
160
|
+
raise ValueError("body must be an object")
|
|
161
|
+
return self._request("POST", f"/agents/{parse.quote(agent_id, safe='')}/runs", body=run_body, **opts)
|
|
162
|
+
|
|
163
|
+
def append_agent_run_event(
|
|
164
|
+
self,
|
|
165
|
+
agent_id: str,
|
|
166
|
+
run_id: str,
|
|
167
|
+
body: Dict[str, Any],
|
|
168
|
+
*,
|
|
169
|
+
expected_prev_chain_hash: str,
|
|
170
|
+
**opts: Any,
|
|
171
|
+
) -> Dict[str, Any]:
|
|
172
|
+
_assert_non_empty_string(agent_id, "agent_id")
|
|
173
|
+
_assert_non_empty_string(run_id, "run_id")
|
|
174
|
+
_assert_non_empty_string(expected_prev_chain_hash, "expected_prev_chain_hash")
|
|
175
|
+
if not isinstance(body, dict):
|
|
176
|
+
raise ValueError("body is required")
|
|
177
|
+
_assert_non_empty_string(body.get("type"), "body.type")
|
|
178
|
+
return self._request(
|
|
179
|
+
"POST",
|
|
180
|
+
f"/agents/{parse.quote(agent_id, safe='')}/runs/{parse.quote(run_id, safe='')}/events",
|
|
181
|
+
body=body,
|
|
182
|
+
expected_prev_chain_hash=expected_prev_chain_hash,
|
|
183
|
+
**opts,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def get_agent_run(self, agent_id: str, run_id: str, **opts: Any) -> Dict[str, Any]:
|
|
187
|
+
_assert_non_empty_string(agent_id, "agent_id")
|
|
188
|
+
_assert_non_empty_string(run_id, "run_id")
|
|
189
|
+
return self._request("GET", f"/agents/{parse.quote(agent_id, safe='')}/runs/{parse.quote(run_id, safe='')}", **opts)
|
|
190
|
+
|
|
191
|
+
def list_agent_run_events(self, agent_id: str, run_id: str, **opts: Any) -> Dict[str, Any]:
|
|
192
|
+
_assert_non_empty_string(agent_id, "agent_id")
|
|
193
|
+
_assert_non_empty_string(run_id, "run_id")
|
|
194
|
+
return self._request("GET", f"/agents/{parse.quote(agent_id, safe='')}/runs/{parse.quote(run_id, safe='')}/events", **opts)
|
|
195
|
+
|
|
196
|
+
def get_run_verification(self, run_id: str, **opts: Any) -> Dict[str, Any]:
|
|
197
|
+
_assert_non_empty_string(run_id, "run_id")
|
|
198
|
+
return self._request("GET", f"/runs/{parse.quote(run_id, safe='')}/verification", **opts)
|
|
199
|
+
|
|
200
|
+
def get_run_settlement(self, run_id: str, **opts: Any) -> Dict[str, Any]:
|
|
201
|
+
_assert_non_empty_string(run_id, "run_id")
|
|
202
|
+
return self._request("GET", f"/runs/{parse.quote(run_id, safe='')}/settlement", **opts)
|
|
203
|
+
|
|
204
|
+
def get_run_settlement_policy_replay(self, run_id: str, **opts: Any) -> Dict[str, Any]:
|
|
205
|
+
_assert_non_empty_string(run_id, "run_id")
|
|
206
|
+
return self._request("GET", f"/runs/{parse.quote(run_id, safe='')}/settlement/policy-replay", **opts)
|
|
207
|
+
|
|
208
|
+
def resolve_run_settlement(self, run_id: str, body: Dict[str, Any], **opts: Any) -> Dict[str, Any]:
|
|
209
|
+
_assert_non_empty_string(run_id, "run_id")
|
|
210
|
+
if not isinstance(body, dict):
|
|
211
|
+
raise ValueError("body is required")
|
|
212
|
+
return self._request("POST", f"/runs/{parse.quote(run_id, safe='')}/settlement/resolve", body=body, **opts)
|
|
213
|
+
|
|
214
|
+
def ops_lock_tool_call_hold(self, body: Dict[str, Any], **opts: Any) -> Dict[str, Any]:
|
|
215
|
+
if not isinstance(body, dict):
|
|
216
|
+
raise ValueError("body is required")
|
|
217
|
+
return self._request("POST", "/ops/tool-calls/holds/lock", body=body, **opts)
|
|
218
|
+
|
|
219
|
+
def ops_list_tool_call_holds(self, query: Optional[Dict[str, Any]] = None, **opts: Any) -> Dict[str, Any]:
|
|
220
|
+
params: Dict[str, Any] = {}
|
|
221
|
+
if isinstance(query, dict):
|
|
222
|
+
for key in ("agreementHash", "status", "limit", "offset"):
|
|
223
|
+
if query.get(key) is not None:
|
|
224
|
+
params[key] = query.get(key)
|
|
225
|
+
suffix = f"?{parse.urlencode(params)}" if params else ""
|
|
226
|
+
return self._request("GET", f"/ops/tool-calls/holds{suffix}", **opts)
|
|
227
|
+
|
|
228
|
+
def ops_get_tool_call_hold(self, hold_hash: str, **opts: Any) -> Dict[str, Any]:
|
|
229
|
+
_assert_non_empty_string(hold_hash, "hold_hash")
|
|
230
|
+
return self._request("GET", f"/ops/tool-calls/holds/{parse.quote(hold_hash, safe='')}", **opts)
|
|
231
|
+
|
|
232
|
+
def ops_run_tool_call_holdback_maintenance(self, body: Optional[Dict[str, Any]] = None, **opts: Any) -> Dict[str, Any]:
|
|
233
|
+
payload = {} if body is None else body
|
|
234
|
+
if not isinstance(payload, dict):
|
|
235
|
+
raise ValueError("body must be an object")
|
|
236
|
+
return self._request("POST", "/ops/maintenance/tool-call-holdback/run", body=payload, **opts)
|
|
237
|
+
|
|
238
|
+
def tool_call_list_arbitration_cases(self, query: Optional[Dict[str, Any]] = None, **opts: Any) -> Dict[str, Any]:
|
|
239
|
+
params: Dict[str, Any] = {}
|
|
240
|
+
if isinstance(query, dict):
|
|
241
|
+
for key in ("agreementHash", "status"):
|
|
242
|
+
if query.get(key) is not None:
|
|
243
|
+
params[key] = query.get(key)
|
|
244
|
+
suffix = f"?{parse.urlencode(params)}" if params else ""
|
|
245
|
+
return self._request("GET", f"/tool-calls/arbitration/cases{suffix}", **opts)
|
|
246
|
+
|
|
247
|
+
def tool_call_get_arbitration_case(self, case_id: str, **opts: Any) -> Dict[str, Any]:
|
|
248
|
+
_assert_non_empty_string(case_id, "case_id")
|
|
249
|
+
return self._request("GET", f"/tool-calls/arbitration/cases/{parse.quote(case_id, safe='')}", **opts)
|
|
250
|
+
|
|
251
|
+
def tool_call_open_arbitration(self, body: Dict[str, Any], **opts: Any) -> Dict[str, Any]:
|
|
252
|
+
if not isinstance(body, dict):
|
|
253
|
+
raise ValueError("body is required")
|
|
254
|
+
return self._request("POST", "/tool-calls/arbitration/open", body=body, **opts)
|
|
255
|
+
|
|
256
|
+
def tool_call_submit_arbitration_verdict(self, body: Dict[str, Any], **opts: Any) -> Dict[str, Any]:
|
|
257
|
+
if not isinstance(body, dict):
|
|
258
|
+
raise ValueError("body is required")
|
|
259
|
+
return self._request("POST", "/tool-calls/arbitration/verdict", body=body, **opts)
|
|
260
|
+
|
|
261
|
+
def ops_get_settlement_adjustment(self, adjustment_id: str, **opts: Any) -> Dict[str, Any]:
|
|
262
|
+
_assert_non_empty_string(adjustment_id, "adjustment_id")
|
|
263
|
+
return self._request("GET", f"/ops/settlement-adjustments/{parse.quote(adjustment_id, safe='')}", **opts)
|
|
264
|
+
|
|
265
|
+
def open_run_dispute(self, run_id: str, body: Optional[Dict[str, Any]] = None, **opts: Any) -> Dict[str, Any]:
|
|
266
|
+
_assert_non_empty_string(run_id, "run_id")
|
|
267
|
+
payload = {} if body is None else body
|
|
268
|
+
if not isinstance(payload, dict):
|
|
269
|
+
raise ValueError("body must be an object")
|
|
270
|
+
return self._request("POST", f"/runs/{parse.quote(run_id, safe='')}/dispute/open", body=payload, **opts)
|
|
271
|
+
|
|
272
|
+
def close_run_dispute(self, run_id: str, body: Optional[Dict[str, Any]] = None, **opts: Any) -> Dict[str, Any]:
|
|
273
|
+
_assert_non_empty_string(run_id, "run_id")
|
|
274
|
+
payload = {} if body is None else body
|
|
275
|
+
if not isinstance(payload, dict):
|
|
276
|
+
raise ValueError("body must be an object")
|
|
277
|
+
return self._request("POST", f"/runs/{parse.quote(run_id, safe='')}/dispute/close", body=payload, **opts)
|
|
278
|
+
|
|
279
|
+
def submit_run_dispute_evidence(self, run_id: str, body: Dict[str, Any], **opts: Any) -> Dict[str, Any]:
|
|
280
|
+
_assert_non_empty_string(run_id, "run_id")
|
|
281
|
+
if not isinstance(body, dict):
|
|
282
|
+
raise ValueError("body is required")
|
|
283
|
+
_assert_non_empty_string(body.get("evidenceRef"), "body.evidenceRef")
|
|
284
|
+
return self._request("POST", f"/runs/{parse.quote(run_id, safe='')}/dispute/evidence", body=body, **opts)
|
|
285
|
+
|
|
286
|
+
def escalate_run_dispute(self, run_id: str, body: Dict[str, Any], **opts: Any) -> Dict[str, Any]:
|
|
287
|
+
_assert_non_empty_string(run_id, "run_id")
|
|
288
|
+
if not isinstance(body, dict):
|
|
289
|
+
raise ValueError("body is required")
|
|
290
|
+
_assert_non_empty_string(body.get("escalationLevel"), "body.escalationLevel")
|
|
291
|
+
return self._request("POST", f"/runs/{parse.quote(run_id, safe='')}/dispute/escalate", body=body, **opts)
|
|
292
|
+
|
|
293
|
+
def create_marketplace_rfq(self, body: Dict[str, Any], **opts: Any) -> Dict[str, Any]:
|
|
294
|
+
if not isinstance(body, dict):
|
|
295
|
+
raise ValueError("body is required")
|
|
296
|
+
return self._request("POST", "/marketplace/rfqs", body=body, **opts)
|
|
297
|
+
|
|
298
|
+
def list_marketplace_rfqs(self, query: Optional[Dict[str, Any]] = None, **opts: Any) -> Dict[str, Any]:
|
|
299
|
+
params = {}
|
|
300
|
+
if isinstance(query, dict):
|
|
301
|
+
for key in ("status", "capability", "posterAgentId", "limit", "offset"):
|
|
302
|
+
if query.get(key) is not None:
|
|
303
|
+
params[key] = query.get(key)
|
|
304
|
+
suffix = f"?{parse.urlencode(params)}" if params else ""
|
|
305
|
+
return self._request("GET", f"/marketplace/rfqs{suffix}", **opts)
|
|
306
|
+
|
|
307
|
+
def submit_marketplace_bid(self, rfq_id: str, body: Dict[str, Any], **opts: Any) -> Dict[str, Any]:
|
|
308
|
+
_assert_non_empty_string(rfq_id, "rfq_id")
|
|
309
|
+
if not isinstance(body, dict):
|
|
310
|
+
raise ValueError("body is required")
|
|
311
|
+
return self._request("POST", f"/marketplace/rfqs/{parse.quote(rfq_id, safe='')}/bids", body=body, **opts)
|
|
312
|
+
|
|
313
|
+
def list_marketplace_bids(self, rfq_id: str, query: Optional[Dict[str, Any]] = None, **opts: Any) -> Dict[str, Any]:
|
|
314
|
+
_assert_non_empty_string(rfq_id, "rfq_id")
|
|
315
|
+
params = {}
|
|
316
|
+
if isinstance(query, dict):
|
|
317
|
+
for key in ("status", "bidderAgentId", "limit", "offset"):
|
|
318
|
+
if query.get(key) is not None:
|
|
319
|
+
params[key] = query.get(key)
|
|
320
|
+
suffix = f"?{parse.urlencode(params)}" if params else ""
|
|
321
|
+
return self._request("GET", f"/marketplace/rfqs/{parse.quote(rfq_id, safe='')}/bids{suffix}", **opts)
|
|
322
|
+
|
|
323
|
+
def accept_marketplace_bid(self, rfq_id: str, body: Dict[str, Any], **opts: Any) -> Dict[str, Any]:
|
|
324
|
+
_assert_non_empty_string(rfq_id, "rfq_id")
|
|
325
|
+
if not isinstance(body, dict):
|
|
326
|
+
raise ValueError("body is required")
|
|
327
|
+
_assert_non_empty_string(body.get("bidId"), "body.bidId")
|
|
328
|
+
return self._request("POST", f"/marketplace/rfqs/{parse.quote(rfq_id, safe='')}/accept", body=body, **opts)
|
|
329
|
+
|
|
330
|
+
def get_tenant_analytics(self, tenant_id: str, query: Optional[Dict[str, Any]] = None, **opts: Any) -> Dict[str, Any]:
|
|
331
|
+
_assert_non_empty_string(tenant_id, "tenant_id")
|
|
332
|
+
params = {}
|
|
333
|
+
if isinstance(query, dict):
|
|
334
|
+
for key in ("month", "bucket", "limit"):
|
|
335
|
+
if query.get(key) is not None:
|
|
336
|
+
params[key] = query.get(key)
|
|
337
|
+
suffix = f"?{parse.urlencode(params)}" if params else ""
|
|
338
|
+
return self._request("GET", f"/v1/tenants/{parse.quote(tenant_id, safe='')}/analytics{suffix}", **opts)
|
|
339
|
+
|
|
340
|
+
def get_tenant_trust_graph(self, tenant_id: str, query: Optional[Dict[str, Any]] = None, **opts: Any) -> Dict[str, Any]:
|
|
341
|
+
_assert_non_empty_string(tenant_id, "tenant_id")
|
|
342
|
+
params = {}
|
|
343
|
+
if isinstance(query, dict):
|
|
344
|
+
for key in ("month", "minRuns", "maxEdges"):
|
|
345
|
+
if query.get(key) is not None:
|
|
346
|
+
params[key] = query.get(key)
|
|
347
|
+
suffix = f"?{parse.urlencode(params)}" if params else ""
|
|
348
|
+
return self._request("GET", f"/v1/tenants/{parse.quote(tenant_id, safe='')}/trust-graph{suffix}", **opts)
|
|
349
|
+
|
|
350
|
+
def list_tenant_trust_graph_snapshots(self, tenant_id: str, query: Optional[Dict[str, Any]] = None, **opts: Any) -> Dict[str, Any]:
|
|
351
|
+
_assert_non_empty_string(tenant_id, "tenant_id")
|
|
352
|
+
params = {}
|
|
353
|
+
if isinstance(query, dict) and query.get("limit") is not None:
|
|
354
|
+
params["limit"] = query.get("limit")
|
|
355
|
+
suffix = f"?{parse.urlencode(params)}" if params else ""
|
|
356
|
+
return self._request("GET", f"/v1/tenants/{parse.quote(tenant_id, safe='')}/trust-graph/snapshots{suffix}", **opts)
|
|
357
|
+
|
|
358
|
+
def create_tenant_trust_graph_snapshot(self, tenant_id: str, body: Optional[Dict[str, Any]] = None, **opts: Any) -> Dict[str, Any]:
|
|
359
|
+
_assert_non_empty_string(tenant_id, "tenant_id")
|
|
360
|
+
payload = {} if body is None else body
|
|
361
|
+
if not isinstance(payload, dict):
|
|
362
|
+
raise ValueError("body must be an object")
|
|
363
|
+
return self._request("POST", f"/v1/tenants/{parse.quote(tenant_id, safe='')}/trust-graph/snapshots", body=payload, **opts)
|
|
364
|
+
|
|
365
|
+
def diff_tenant_trust_graph(self, tenant_id: str, query: Optional[Dict[str, Any]] = None, **opts: Any) -> Dict[str, Any]:
|
|
366
|
+
_assert_non_empty_string(tenant_id, "tenant_id")
|
|
367
|
+
params = {}
|
|
368
|
+
if isinstance(query, dict):
|
|
369
|
+
for key in ("baseMonth", "compareMonth", "limit", "minRuns", "maxEdges", "includeUnchanged"):
|
|
370
|
+
if query.get(key) is not None:
|
|
371
|
+
params[key] = query.get(key)
|
|
372
|
+
suffix = f"?{parse.urlencode(params)}" if params else ""
|
|
373
|
+
return self._request("GET", f"/v1/tenants/{parse.quote(tenant_id, safe='')}/trust-graph/diff{suffix}", **opts)
|
|
374
|
+
|
|
375
|
+
def first_paid_rfq(
|
|
376
|
+
self,
|
|
377
|
+
params: Dict[str, Any],
|
|
378
|
+
*,
|
|
379
|
+
idempotency_prefix: Optional[str] = None,
|
|
380
|
+
request_id_prefix: Optional[str] = None,
|
|
381
|
+
timeout_seconds: Optional[float] = None,
|
|
382
|
+
) -> Dict[str, Any]:
|
|
383
|
+
if not isinstance(params, dict):
|
|
384
|
+
raise ValueError("params must be an object")
|
|
385
|
+
|
|
386
|
+
poster_agent = params.get("poster_agent")
|
|
387
|
+
bidder_agent = params.get("bidder_agent")
|
|
388
|
+
if not isinstance(poster_agent, dict):
|
|
389
|
+
raise ValueError("params.poster_agent is required")
|
|
390
|
+
if not isinstance(bidder_agent, dict):
|
|
391
|
+
raise ValueError("params.bidder_agent is required")
|
|
392
|
+
_assert_non_empty_string(poster_agent.get("publicKeyPem"), "params.poster_agent.publicKeyPem")
|
|
393
|
+
_assert_non_empty_string(bidder_agent.get("publicKeyPem"), "params.bidder_agent.publicKeyPem")
|
|
394
|
+
|
|
395
|
+
step_prefix = _normalize_prefix(
|
|
396
|
+
idempotency_prefix,
|
|
397
|
+
f"sdk_first_paid_rfq_{int(time.time() * 1000):x}_{random.randint(0, 0xFFFFFF):06x}",
|
|
398
|
+
)
|
|
399
|
+
request_prefix = _normalize_prefix(request_id_prefix, _random_request_id())
|
|
400
|
+
|
|
401
|
+
def make_step_opts(step: str, **extra: Any) -> Dict[str, Any]:
|
|
402
|
+
out = {
|
|
403
|
+
"request_id": f"{request_prefix}_{step}",
|
|
404
|
+
"idempotency_key": f"{step_prefix}_{step}",
|
|
405
|
+
"timeout_seconds": timeout_seconds,
|
|
406
|
+
}
|
|
407
|
+
out.update(extra)
|
|
408
|
+
return out
|
|
409
|
+
|
|
410
|
+
poster_registration = self.register_agent(poster_agent, **make_step_opts("register_poster"))
|
|
411
|
+
poster_agent_id = poster_registration.get("body", {}).get("agentIdentity", {}).get("agentId")
|
|
412
|
+
_assert_non_empty_string(poster_agent_id, "poster_agent_id")
|
|
413
|
+
|
|
414
|
+
bidder_registration = self.register_agent(bidder_agent, **make_step_opts("register_bidder"))
|
|
415
|
+
bidder_agent_id = bidder_registration.get("body", {}).get("agentIdentity", {}).get("agentId")
|
|
416
|
+
_assert_non_empty_string(bidder_agent_id, "bidder_agent_id")
|
|
417
|
+
|
|
418
|
+
accepted_by_registration = None
|
|
419
|
+
accepted_by_agent_id = poster_agent_id
|
|
420
|
+
accepted_by_agent = params.get("accepted_by_agent")
|
|
421
|
+
if accepted_by_agent is not None:
|
|
422
|
+
if not isinstance(accepted_by_agent, dict):
|
|
423
|
+
raise ValueError("params.accepted_by_agent must be an object")
|
|
424
|
+
_assert_non_empty_string(accepted_by_agent.get("publicKeyPem"), "params.accepted_by_agent.publicKeyPem")
|
|
425
|
+
accepted_by_registration = self.register_agent(accepted_by_agent, **make_step_opts("register_accepting_agent"))
|
|
426
|
+
accepted_by_agent_id = accepted_by_registration.get("body", {}).get("agentIdentity", {}).get("agentId")
|
|
427
|
+
_assert_non_empty_string(accepted_by_agent_id, "accepted_by_agent_id")
|
|
428
|
+
|
|
429
|
+
payer_credit = params.get("payer_credit")
|
|
430
|
+
credit_result = None
|
|
431
|
+
if payer_credit is not None:
|
|
432
|
+
if not isinstance(payer_credit, dict):
|
|
433
|
+
raise ValueError("params.payer_credit must be an object")
|
|
434
|
+
amount_cents = payer_credit.get("amountCents")
|
|
435
|
+
if not isinstance(amount_cents, (int, float)) or amount_cents <= 0:
|
|
436
|
+
raise ValueError("params.payer_credit.amountCents must be a positive number")
|
|
437
|
+
credit_result = self.credit_agent_wallet(
|
|
438
|
+
poster_agent_id,
|
|
439
|
+
{
|
|
440
|
+
"amountCents": int(amount_cents),
|
|
441
|
+
"currency": payer_credit.get("currency", "USD"),
|
|
442
|
+
},
|
|
443
|
+
**make_step_opts("credit_poster_wallet"),
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
rfq_defaults = {
|
|
447
|
+
"rfqId": f"rfq_{step_prefix}",
|
|
448
|
+
"title": "SDK paid rfq",
|
|
449
|
+
"capability": "general",
|
|
450
|
+
"posterAgentId": poster_agent_id,
|
|
451
|
+
"budgetCents": 1000,
|
|
452
|
+
"currency": "USD",
|
|
453
|
+
}
|
|
454
|
+
rfq_body = {**rfq_defaults, **(params.get("rfq") if isinstance(params.get("rfq"), dict) else {})}
|
|
455
|
+
rfq_body["posterAgentId"] = poster_agent_id
|
|
456
|
+
create_rfq = self.create_marketplace_rfq(rfq_body, **make_step_opts("create_rfq"))
|
|
457
|
+
rfq = create_rfq.get("body", {}).get("rfq", {}) if isinstance(create_rfq.get("body"), dict) else {}
|
|
458
|
+
rfq_id = rfq.get("rfqId")
|
|
459
|
+
_assert_non_empty_string(rfq_id, "rfq_id")
|
|
460
|
+
|
|
461
|
+
bid_defaults = {
|
|
462
|
+
"bidId": f"bid_{step_prefix}",
|
|
463
|
+
"bidderAgentId": bidder_agent_id,
|
|
464
|
+
"amountCents": int(rfq_body.get("budgetCents", 1000)),
|
|
465
|
+
"currency": str(rfq_body.get("currency", "USD")),
|
|
466
|
+
"etaSeconds": 900,
|
|
467
|
+
}
|
|
468
|
+
bid_body = {**bid_defaults, **(params.get("bid") if isinstance(params.get("bid"), dict) else {})}
|
|
469
|
+
bid_body["bidderAgentId"] = bidder_agent_id
|
|
470
|
+
submit_bid = self.submit_marketplace_bid(rfq_id, bid_body, **make_step_opts("submit_bid"))
|
|
471
|
+
bid = submit_bid.get("body", {}).get("bid", {}) if isinstance(submit_bid.get("body"), dict) else {}
|
|
472
|
+
bid_id = bid.get("bidId")
|
|
473
|
+
_assert_non_empty_string(bid_id, "bid_id")
|
|
474
|
+
|
|
475
|
+
settlement_config = params.get("settlement") if isinstance(params.get("settlement"), dict) else {}
|
|
476
|
+
accept_defaults = {
|
|
477
|
+
"bidId": bid_id,
|
|
478
|
+
"acceptedByAgentId": accepted_by_agent_id,
|
|
479
|
+
"settlement": {
|
|
480
|
+
"payerAgentId": poster_agent_id,
|
|
481
|
+
"amountCents": int(bid_body.get("amountCents")),
|
|
482
|
+
"currency": str(bid_body.get("currency", rfq_body.get("currency", "USD"))),
|
|
483
|
+
},
|
|
484
|
+
}
|
|
485
|
+
accept_body = {**accept_defaults, **(params.get("accept") if isinstance(params.get("accept"), dict) else {})}
|
|
486
|
+
if not isinstance(accept_body.get("settlement"), dict):
|
|
487
|
+
accept_body["settlement"] = {}
|
|
488
|
+
accept_body["settlement"] = {**accept_defaults["settlement"], **accept_body["settlement"], **settlement_config}
|
|
489
|
+
accept_bid = self.accept_marketplace_bid(rfq_id, accept_body, **make_step_opts("accept_bid"))
|
|
490
|
+
accepted_body = accept_bid.get("body", {}) if isinstance(accept_bid.get("body"), dict) else {}
|
|
491
|
+
run = accepted_body.get("run", {}) if isinstance(accepted_body.get("run"), dict) else {}
|
|
492
|
+
run_id = run.get("runId")
|
|
493
|
+
_assert_non_empty_string(run_id, "run_id")
|
|
494
|
+
|
|
495
|
+
final_event = None
|
|
496
|
+
final_run = run
|
|
497
|
+
final_settlement = accepted_body.get("settlement")
|
|
498
|
+
if params.get("auto_complete", True):
|
|
499
|
+
prev_chain_hash = run.get("lastChainHash")
|
|
500
|
+
_assert_non_empty_string(prev_chain_hash, "run.lastChainHash")
|
|
501
|
+
completed_payload = dict(params.get("completed_payload") or {})
|
|
502
|
+
completed_payload.setdefault("outputRef", f"evidence://{run_id}/result.json")
|
|
503
|
+
if isinstance(params.get("completed_metrics"), dict):
|
|
504
|
+
completed_payload["metrics"] = params.get("completed_metrics")
|
|
505
|
+
elif "metrics" not in completed_payload:
|
|
506
|
+
completed_payload["metrics"] = {"settlementReleaseRatePct": 100}
|
|
507
|
+
completed = self.append_agent_run_event(
|
|
508
|
+
bidder_agent_id,
|
|
509
|
+
run_id,
|
|
510
|
+
{"type": "RUN_COMPLETED", "actor": {"type": "agent", "id": bidder_agent_id}, "payload": completed_payload},
|
|
511
|
+
expected_prev_chain_hash=prev_chain_hash,
|
|
512
|
+
**make_step_opts("run_completed"),
|
|
513
|
+
)
|
|
514
|
+
completed_body = completed.get("body", {}) if isinstance(completed.get("body"), dict) else {}
|
|
515
|
+
final_event = completed_body.get("event")
|
|
516
|
+
final_run = completed_body.get("run", final_run)
|
|
517
|
+
final_settlement = completed_body.get("settlement", final_settlement)
|
|
518
|
+
|
|
519
|
+
verification = self.get_run_verification(run_id, **make_step_opts("verification", idempotency_key=None))
|
|
520
|
+
settlement = self.get_run_settlement(run_id, **make_step_opts("settlement", idempotency_key=None))
|
|
521
|
+
|
|
522
|
+
return {
|
|
523
|
+
"ids": {
|
|
524
|
+
"poster_agent_id": poster_agent_id,
|
|
525
|
+
"bidder_agent_id": bidder_agent_id,
|
|
526
|
+
"accepted_by_agent_id": accepted_by_agent_id,
|
|
527
|
+
"rfq_id": rfq_id,
|
|
528
|
+
"bid_id": bid_id,
|
|
529
|
+
"run_id": run_id,
|
|
530
|
+
},
|
|
531
|
+
"poster_registration": poster_registration,
|
|
532
|
+
"bidder_registration": bidder_registration,
|
|
533
|
+
"accepted_by_registration": accepted_by_registration,
|
|
534
|
+
"payer_credit": credit_result,
|
|
535
|
+
"create_rfq": create_rfq,
|
|
536
|
+
"submit_bid": submit_bid,
|
|
537
|
+
"accept_bid": accept_bid,
|
|
538
|
+
"final_event": final_event,
|
|
539
|
+
"final_run": final_run,
|
|
540
|
+
"final_settlement": final_settlement,
|
|
541
|
+
"verification": verification,
|
|
542
|
+
"settlement": settlement,
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
def first_verified_run(
|
|
546
|
+
self,
|
|
547
|
+
params: Dict[str, Any],
|
|
548
|
+
*,
|
|
549
|
+
idempotency_prefix: Optional[str] = None,
|
|
550
|
+
request_id_prefix: Optional[str] = None,
|
|
551
|
+
timeout_seconds: Optional[float] = None,
|
|
552
|
+
) -> Dict[str, Any]:
|
|
553
|
+
if not isinstance(params, dict):
|
|
554
|
+
raise ValueError("params must be an object")
|
|
555
|
+
payee_agent = params.get("payee_agent")
|
|
556
|
+
if not isinstance(payee_agent, dict):
|
|
557
|
+
raise ValueError("params.payee_agent is required")
|
|
558
|
+
_assert_non_empty_string(payee_agent.get("publicKeyPem"), "params.payee_agent.publicKeyPem")
|
|
559
|
+
|
|
560
|
+
step_prefix = _normalize_prefix(
|
|
561
|
+
idempotency_prefix,
|
|
562
|
+
f"sdk_first_verified_run_{int(time.time() * 1000):x}_{random.randint(0, 0xFFFFFF):06x}",
|
|
563
|
+
)
|
|
564
|
+
request_prefix = _normalize_prefix(request_id_prefix, _random_request_id())
|
|
565
|
+
|
|
566
|
+
def make_step_opts(step: str, **extra: Any) -> Dict[str, Any]:
|
|
567
|
+
out = {
|
|
568
|
+
"request_id": f"{request_prefix}_{step}",
|
|
569
|
+
"idempotency_key": f"{step_prefix}_{step}",
|
|
570
|
+
"timeout_seconds": timeout_seconds,
|
|
571
|
+
}
|
|
572
|
+
out.update(extra)
|
|
573
|
+
return out
|
|
574
|
+
|
|
575
|
+
payee_registration = self.register_agent(payee_agent, **make_step_opts("register_payee"))
|
|
576
|
+
payee_agent_id = payee_registration.get("body", {}).get("agentIdentity", {}).get("agentId")
|
|
577
|
+
_assert_non_empty_string(payee_agent_id, "payee_agent_id")
|
|
578
|
+
|
|
579
|
+
payer_registration = None
|
|
580
|
+
payer_credit = None
|
|
581
|
+
payer_agent_id = None
|
|
582
|
+
payer_agent = params.get("payer_agent")
|
|
583
|
+
if payer_agent is not None:
|
|
584
|
+
if not isinstance(payer_agent, dict):
|
|
585
|
+
raise ValueError("params.payer_agent must be an object")
|
|
586
|
+
_assert_non_empty_string(payer_agent.get("publicKeyPem"), "params.payer_agent.publicKeyPem")
|
|
587
|
+
payer_registration = self.register_agent(payer_agent, **make_step_opts("register_payer"))
|
|
588
|
+
payer_agent_id = payer_registration.get("body", {}).get("agentIdentity", {}).get("agentId")
|
|
589
|
+
_assert_non_empty_string(payer_agent_id, "payer_agent_id")
|
|
590
|
+
|
|
591
|
+
settlement = params.get("settlement") if isinstance(params.get("settlement"), dict) else None
|
|
592
|
+
settlement_amount_cents = settlement.get("amountCents") if settlement else None
|
|
593
|
+
settlement_currency = settlement.get("currency", "USD") if settlement else "USD"
|
|
594
|
+
settlement_payer_agent_id = (
|
|
595
|
+
settlement.get("payerAgentId")
|
|
596
|
+
if settlement and isinstance(settlement.get("payerAgentId"), str)
|
|
597
|
+
else payer_agent_id
|
|
598
|
+
)
|
|
599
|
+
if settlement_amount_cents is not None and settlement_payer_agent_id is None:
|
|
600
|
+
raise ValueError("params.payer_agent or params.settlement.payerAgentId is required when settlement is requested")
|
|
601
|
+
|
|
602
|
+
payer_credit_input = params.get("payer_credit")
|
|
603
|
+
if payer_credit_input is not None:
|
|
604
|
+
if not isinstance(payer_credit_input, dict):
|
|
605
|
+
raise ValueError("params.payer_credit must be an object")
|
|
606
|
+
payer_credit_amount = payer_credit_input.get("amountCents")
|
|
607
|
+
if not isinstance(payer_credit_amount, (int, float)) or payer_credit_amount <= 0:
|
|
608
|
+
raise ValueError("params.payer_credit.amountCents must be a positive number")
|
|
609
|
+
if not payer_agent_id:
|
|
610
|
+
raise ValueError("params.payer_agent is required when params.payer_credit is provided")
|
|
611
|
+
payer_credit = self.credit_agent_wallet(
|
|
612
|
+
payer_agent_id,
|
|
613
|
+
{
|
|
614
|
+
"amountCents": int(payer_credit_amount),
|
|
615
|
+
"currency": payer_credit_input.get("currency", settlement_currency),
|
|
616
|
+
},
|
|
617
|
+
**make_step_opts("credit_payer_wallet"),
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
run_body = dict(params.get("run") or {})
|
|
621
|
+
if settlement_amount_cents is not None:
|
|
622
|
+
if not isinstance(settlement_amount_cents, (int, float)) or settlement_amount_cents <= 0:
|
|
623
|
+
raise ValueError("params.settlement.amountCents must be a positive number")
|
|
624
|
+
run_body["settlement"] = {
|
|
625
|
+
"payerAgentId": settlement_payer_agent_id,
|
|
626
|
+
"amountCents": int(settlement_amount_cents),
|
|
627
|
+
"currency": settlement_currency,
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
run_created = self.create_agent_run(payee_agent_id, run_body, **make_step_opts("create_run"))
|
|
631
|
+
run_id = run_created.get("body", {}).get("run", {}).get("runId")
|
|
632
|
+
_assert_non_empty_string(run_id, "run_id")
|
|
633
|
+
prev_chain_hash = run_created.get("body", {}).get("run", {}).get("lastChainHash")
|
|
634
|
+
_assert_non_empty_string(prev_chain_hash, "run_created.body.run.lastChainHash")
|
|
635
|
+
|
|
636
|
+
actor = params.get("actor") or {"type": "agent", "id": payee_agent_id}
|
|
637
|
+
started_payload = params.get("started_payload") or {"startedBy": "sdk.first_verified_run"}
|
|
638
|
+
run_started = self.append_agent_run_event(
|
|
639
|
+
payee_agent_id,
|
|
640
|
+
run_id,
|
|
641
|
+
{"type": "RUN_STARTED", "actor": actor, "payload": started_payload},
|
|
642
|
+
expected_prev_chain_hash=prev_chain_hash,
|
|
643
|
+
**make_step_opts("run_started"),
|
|
644
|
+
)
|
|
645
|
+
prev_chain_hash = run_started.get("body", {}).get("run", {}).get("lastChainHash")
|
|
646
|
+
_assert_non_empty_string(prev_chain_hash, "run_started.body.run.lastChainHash")
|
|
647
|
+
|
|
648
|
+
evidence_ref = params.get("evidence_ref") if isinstance(params.get("evidence_ref"), str) and params.get("evidence_ref").strip() else f"evidence://{run_id}/output.json"
|
|
649
|
+
evidence_payload = params.get("evidence_payload") if isinstance(params.get("evidence_payload"), dict) else {"evidenceRef": evidence_ref}
|
|
650
|
+
run_evidence_added = self.append_agent_run_event(
|
|
651
|
+
payee_agent_id,
|
|
652
|
+
run_id,
|
|
653
|
+
{"type": "EVIDENCE_ADDED", "actor": actor, "payload": evidence_payload},
|
|
654
|
+
expected_prev_chain_hash=prev_chain_hash,
|
|
655
|
+
**make_step_opts("evidence_added"),
|
|
656
|
+
)
|
|
657
|
+
prev_chain_hash = run_evidence_added.get("body", {}).get("run", {}).get("lastChainHash")
|
|
658
|
+
_assert_non_empty_string(prev_chain_hash, "run_evidence_added.body.run.lastChainHash")
|
|
659
|
+
|
|
660
|
+
completed_payload = dict(params.get("completed_payload") or {})
|
|
661
|
+
output_ref = params.get("output_ref") if isinstance(params.get("output_ref"), str) and params.get("output_ref").strip() else evidence_ref
|
|
662
|
+
completed_payload["outputRef"] = output_ref
|
|
663
|
+
if isinstance(params.get("completed_metrics"), dict):
|
|
664
|
+
completed_payload["metrics"] = params.get("completed_metrics")
|
|
665
|
+
run_completed = self.append_agent_run_event(
|
|
666
|
+
payee_agent_id,
|
|
667
|
+
run_id,
|
|
668
|
+
{"type": "RUN_COMPLETED", "actor": actor, "payload": completed_payload},
|
|
669
|
+
expected_prev_chain_hash=prev_chain_hash,
|
|
670
|
+
**make_step_opts("run_completed"),
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
run = self.get_agent_run(payee_agent_id, run_id, **make_step_opts("get_run"))
|
|
674
|
+
verification = self.get_run_verification(run_id, **make_step_opts("get_verification"))
|
|
675
|
+
settlement_out = None
|
|
676
|
+
if run_body.get("settlement") or run_created.get("body", {}).get("settlement") or run_completed.get("body", {}).get("settlement"):
|
|
677
|
+
settlement_out = self.get_run_settlement(run_id, **make_step_opts("get_settlement"))
|
|
678
|
+
|
|
679
|
+
return {
|
|
680
|
+
"ids": {"run_id": run_id, "payee_agent_id": payee_agent_id, "payer_agent_id": payer_agent_id},
|
|
681
|
+
"payee_registration": payee_registration,
|
|
682
|
+
"payer_registration": payer_registration,
|
|
683
|
+
"payer_credit": payer_credit,
|
|
684
|
+
"run_created": run_created,
|
|
685
|
+
"run_started": run_started,
|
|
686
|
+
"run_evidence_added": run_evidence_added,
|
|
687
|
+
"run_completed": run_completed,
|
|
688
|
+
"run": run,
|
|
689
|
+
"verification": verification,
|
|
690
|
+
"settlement": settlement_out,
|
|
691
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: settld-api-sdk-python
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Settld API SDK (Python)
|
|
5
|
+
Author: Settld
|
|
6
|
+
License: UNLICENSED
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# Settld API SDK (Python)
|
|
11
|
+
|
|
12
|
+
Python client for Settld API endpoints, including high-level helpers:
|
|
13
|
+
- `first_verified_run` (register agents, run work, verify, settle)
|
|
14
|
+
- `first_paid_rfq` (rfq -> bid -> accept -> run -> settlement)
|
|
15
|
+
- run settlement/dispute lifecycle: `get_run_settlement_policy_replay`, `resolve_run_settlement`, `open_run_dispute`, `submit_run_dispute_evidence`, `escalate_run_dispute`, `close_run_dispute`
|
|
16
|
+
- `get_tenant_analytics` / `get_tenant_trust_graph`
|
|
17
|
+
- `list_tenant_trust_graph_snapshots` / `create_tenant_trust_graph_snapshot` / `diff_tenant_trust_graph`
|
|
18
|
+
- auth headers: `api_key` (Bearer) and optional `x_api_key` (Magic Link)
|
|
19
|
+
|
|
20
|
+
Quickstart docs live in `docs/QUICKSTART_SDK_PYTHON.md` at repo root.
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: settld-api-sdk-python
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: Settld API SDK (Python)
|
|
5
|
-
Author: Settld
|
|
6
|
-
License: UNLICENSED
|
|
7
|
-
Requires-Python: >=3.9
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
|
|
10
|
-
# Settld API SDK (Python)
|
|
11
|
-
|
|
12
|
-
Python client for Settld API endpoints, including a high-level `first_verified_run` helper.
|
|
13
|
-
|
|
14
|
-
Quickstart docs live in `docs/QUICKSTART_SDK_PYTHON.md` at repo root.
|
|
@@ -1,346 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
import random
|
|
5
|
-
import time
|
|
6
|
-
import uuid
|
|
7
|
-
from typing import Any, Dict, Optional
|
|
8
|
-
from urllib import error, parse, request
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def _assert_non_empty_string(value: Any, name: str) -> str:
|
|
12
|
-
if not isinstance(value, str) or value.strip() == "":
|
|
13
|
-
raise ValueError(f"{name} must be a non-empty string")
|
|
14
|
-
return value
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def _random_request_id() -> str:
|
|
18
|
-
return f"req_{uuid.uuid4().hex}"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def _normalize_prefix(value: Optional[str], fallback: str) -> str:
|
|
22
|
-
if isinstance(value, str) and value.strip():
|
|
23
|
-
return value.strip()
|
|
24
|
-
return fallback
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class SettldApiError(Exception):
|
|
28
|
-
def __init__(
|
|
29
|
-
self,
|
|
30
|
-
*,
|
|
31
|
-
status: int,
|
|
32
|
-
message: str,
|
|
33
|
-
code: Optional[str] = None,
|
|
34
|
-
details: Any = None,
|
|
35
|
-
request_id: Optional[str] = None,
|
|
36
|
-
) -> None:
|
|
37
|
-
super().__init__(message)
|
|
38
|
-
self.status = status
|
|
39
|
-
self.code = code
|
|
40
|
-
self.details = details
|
|
41
|
-
self.request_id = request_id
|
|
42
|
-
|
|
43
|
-
def to_dict(self) -> Dict[str, Any]:
|
|
44
|
-
return {
|
|
45
|
-
"status": self.status,
|
|
46
|
-
"code": self.code,
|
|
47
|
-
"message": str(self),
|
|
48
|
-
"details": self.details,
|
|
49
|
-
"requestId": self.request_id,
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
class SettldClient:
|
|
54
|
-
def __init__(
|
|
55
|
-
self,
|
|
56
|
-
*,
|
|
57
|
-
base_url: str,
|
|
58
|
-
tenant_id: str,
|
|
59
|
-
protocol: str = "1.0",
|
|
60
|
-
api_key: Optional[str] = None,
|
|
61
|
-
user_agent: Optional[str] = None,
|
|
62
|
-
timeout_seconds: float = 30.0,
|
|
63
|
-
) -> None:
|
|
64
|
-
self.base_url = _assert_non_empty_string(base_url, "base_url").rstrip("/")
|
|
65
|
-
self.tenant_id = _assert_non_empty_string(tenant_id, "tenant_id")
|
|
66
|
-
self.protocol = protocol
|
|
67
|
-
self.api_key = api_key
|
|
68
|
-
self.user_agent = user_agent
|
|
69
|
-
self.timeout_seconds = timeout_seconds
|
|
70
|
-
|
|
71
|
-
def _request(
|
|
72
|
-
self,
|
|
73
|
-
method: str,
|
|
74
|
-
path: str,
|
|
75
|
-
*,
|
|
76
|
-
body: Optional[Dict[str, Any]] = None,
|
|
77
|
-
request_id: Optional[str] = None,
|
|
78
|
-
idempotency_key: Optional[str] = None,
|
|
79
|
-
expected_prev_chain_hash: Optional[str] = None,
|
|
80
|
-
timeout_seconds: Optional[float] = None,
|
|
81
|
-
) -> Dict[str, Any]:
|
|
82
|
-
rid = request_id if request_id else _random_request_id()
|
|
83
|
-
headers = {
|
|
84
|
-
"content-type": "application/json",
|
|
85
|
-
"x-proxy-tenant-id": self.tenant_id,
|
|
86
|
-
"x-settld-protocol": self.protocol,
|
|
87
|
-
"x-request-id": rid,
|
|
88
|
-
}
|
|
89
|
-
if self.user_agent:
|
|
90
|
-
headers["user-agent"] = self.user_agent
|
|
91
|
-
if self.api_key:
|
|
92
|
-
headers["authorization"] = f"Bearer {self.api_key}"
|
|
93
|
-
if idempotency_key:
|
|
94
|
-
headers["x-idempotency-key"] = str(idempotency_key)
|
|
95
|
-
if expected_prev_chain_hash:
|
|
96
|
-
headers["x-proxy-expected-prev-chain-hash"] = str(expected_prev_chain_hash)
|
|
97
|
-
|
|
98
|
-
url = parse.urljoin(f"{self.base_url}/", path.lstrip("/"))
|
|
99
|
-
payload = None if body is None else json.dumps(body).encode("utf-8")
|
|
100
|
-
req = request.Request(url=url, data=payload, method=method, headers=headers)
|
|
101
|
-
timeout = self.timeout_seconds if timeout_seconds is None else timeout_seconds
|
|
102
|
-
try:
|
|
103
|
-
with request.urlopen(req, timeout=timeout) as response:
|
|
104
|
-
raw = response.read().decode("utf-8")
|
|
105
|
-
parsed = None
|
|
106
|
-
if raw:
|
|
107
|
-
try:
|
|
108
|
-
parsed = json.loads(raw)
|
|
109
|
-
except json.JSONDecodeError:
|
|
110
|
-
parsed = {"raw": raw}
|
|
111
|
-
response_headers = {str(k).lower(): str(v) for k, v in response.headers.items()}
|
|
112
|
-
return {
|
|
113
|
-
"ok": True,
|
|
114
|
-
"status": int(response.status),
|
|
115
|
-
"requestId": response_headers.get("x-request-id"),
|
|
116
|
-
"body": parsed,
|
|
117
|
-
"headers": response_headers,
|
|
118
|
-
}
|
|
119
|
-
except error.HTTPError as http_error:
|
|
120
|
-
raw = http_error.read().decode("utf-8")
|
|
121
|
-
parsed: Any = {}
|
|
122
|
-
if raw:
|
|
123
|
-
try:
|
|
124
|
-
parsed = json.loads(raw)
|
|
125
|
-
except json.JSONDecodeError:
|
|
126
|
-
parsed = {"raw": raw}
|
|
127
|
-
response_headers = {str(k).lower(): str(v) for k, v in http_error.headers.items()}
|
|
128
|
-
raise SettldApiError(
|
|
129
|
-
status=int(http_error.code),
|
|
130
|
-
code=parsed.get("code") if isinstance(parsed, dict) else None,
|
|
131
|
-
message=parsed.get("error", f"request failed ({http_error.code})") if isinstance(parsed, dict) else f"request failed ({http_error.code})",
|
|
132
|
-
details=parsed.get("details") if isinstance(parsed, dict) else None,
|
|
133
|
-
request_id=response_headers.get("x-request-id"),
|
|
134
|
-
) from http_error
|
|
135
|
-
|
|
136
|
-
def register_agent(self, body: Dict[str, Any], **opts: Any) -> Dict[str, Any]:
|
|
137
|
-
if not isinstance(body, dict):
|
|
138
|
-
raise ValueError("body is required")
|
|
139
|
-
_assert_non_empty_string(body.get("publicKeyPem"), "body.publicKeyPem")
|
|
140
|
-
return self._request("POST", "/agents/register", body=body, **opts)
|
|
141
|
-
|
|
142
|
-
def credit_agent_wallet(self, agent_id: str, body: Dict[str, Any], **opts: Any) -> Dict[str, Any]:
|
|
143
|
-
_assert_non_empty_string(agent_id, "agent_id")
|
|
144
|
-
if not isinstance(body, dict):
|
|
145
|
-
raise ValueError("body is required")
|
|
146
|
-
return self._request("POST", f"/agents/{parse.quote(agent_id, safe='')}/wallet/credit", body=body, **opts)
|
|
147
|
-
|
|
148
|
-
def get_agent_wallet(self, agent_id: str, **opts: Any) -> Dict[str, Any]:
|
|
149
|
-
_assert_non_empty_string(agent_id, "agent_id")
|
|
150
|
-
return self._request("GET", f"/agents/{parse.quote(agent_id, safe='')}/wallet", **opts)
|
|
151
|
-
|
|
152
|
-
def create_agent_run(self, agent_id: str, body: Optional[Dict[str, Any]] = None, **opts: Any) -> Dict[str, Any]:
|
|
153
|
-
_assert_non_empty_string(agent_id, "agent_id")
|
|
154
|
-
run_body = {} if body is None else body
|
|
155
|
-
if not isinstance(run_body, dict):
|
|
156
|
-
raise ValueError("body must be an object")
|
|
157
|
-
return self._request("POST", f"/agents/{parse.quote(agent_id, safe='')}/runs", body=run_body, **opts)
|
|
158
|
-
|
|
159
|
-
def append_agent_run_event(
|
|
160
|
-
self,
|
|
161
|
-
agent_id: str,
|
|
162
|
-
run_id: str,
|
|
163
|
-
body: Dict[str, Any],
|
|
164
|
-
*,
|
|
165
|
-
expected_prev_chain_hash: str,
|
|
166
|
-
**opts: Any,
|
|
167
|
-
) -> Dict[str, Any]:
|
|
168
|
-
_assert_non_empty_string(agent_id, "agent_id")
|
|
169
|
-
_assert_non_empty_string(run_id, "run_id")
|
|
170
|
-
_assert_non_empty_string(expected_prev_chain_hash, "expected_prev_chain_hash")
|
|
171
|
-
if not isinstance(body, dict):
|
|
172
|
-
raise ValueError("body is required")
|
|
173
|
-
_assert_non_empty_string(body.get("type"), "body.type")
|
|
174
|
-
return self._request(
|
|
175
|
-
"POST",
|
|
176
|
-
f"/agents/{parse.quote(agent_id, safe='')}/runs/{parse.quote(run_id, safe='')}/events",
|
|
177
|
-
body=body,
|
|
178
|
-
expected_prev_chain_hash=expected_prev_chain_hash,
|
|
179
|
-
**opts,
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
def get_agent_run(self, agent_id: str, run_id: str, **opts: Any) -> Dict[str, Any]:
|
|
183
|
-
_assert_non_empty_string(agent_id, "agent_id")
|
|
184
|
-
_assert_non_empty_string(run_id, "run_id")
|
|
185
|
-
return self._request("GET", f"/agents/{parse.quote(agent_id, safe='')}/runs/{parse.quote(run_id, safe='')}", **opts)
|
|
186
|
-
|
|
187
|
-
def list_agent_run_events(self, agent_id: str, run_id: str, **opts: Any) -> Dict[str, Any]:
|
|
188
|
-
_assert_non_empty_string(agent_id, "agent_id")
|
|
189
|
-
_assert_non_empty_string(run_id, "run_id")
|
|
190
|
-
return self._request("GET", f"/agents/{parse.quote(agent_id, safe='')}/runs/{parse.quote(run_id, safe='')}/events", **opts)
|
|
191
|
-
|
|
192
|
-
def get_run_verification(self, run_id: str, **opts: Any) -> Dict[str, Any]:
|
|
193
|
-
_assert_non_empty_string(run_id, "run_id")
|
|
194
|
-
return self._request("GET", f"/runs/{parse.quote(run_id, safe='')}/verification", **opts)
|
|
195
|
-
|
|
196
|
-
def get_run_settlement(self, run_id: str, **opts: Any) -> Dict[str, Any]:
|
|
197
|
-
_assert_non_empty_string(run_id, "run_id")
|
|
198
|
-
return self._request("GET", f"/runs/{parse.quote(run_id, safe='')}/settlement", **opts)
|
|
199
|
-
|
|
200
|
-
def first_verified_run(
|
|
201
|
-
self,
|
|
202
|
-
params: Dict[str, Any],
|
|
203
|
-
*,
|
|
204
|
-
idempotency_prefix: Optional[str] = None,
|
|
205
|
-
request_id_prefix: Optional[str] = None,
|
|
206
|
-
timeout_seconds: Optional[float] = None,
|
|
207
|
-
) -> Dict[str, Any]:
|
|
208
|
-
if not isinstance(params, dict):
|
|
209
|
-
raise ValueError("params must be an object")
|
|
210
|
-
payee_agent = params.get("payee_agent")
|
|
211
|
-
if not isinstance(payee_agent, dict):
|
|
212
|
-
raise ValueError("params.payee_agent is required")
|
|
213
|
-
_assert_non_empty_string(payee_agent.get("publicKeyPem"), "params.payee_agent.publicKeyPem")
|
|
214
|
-
|
|
215
|
-
step_prefix = _normalize_prefix(
|
|
216
|
-
idempotency_prefix,
|
|
217
|
-
f"sdk_first_verified_run_{int(time.time() * 1000):x}_{random.randint(0, 0xFFFFFF):06x}",
|
|
218
|
-
)
|
|
219
|
-
request_prefix = _normalize_prefix(request_id_prefix, _random_request_id())
|
|
220
|
-
|
|
221
|
-
def make_step_opts(step: str, **extra: Any) -> Dict[str, Any]:
|
|
222
|
-
out = {
|
|
223
|
-
"request_id": f"{request_prefix}_{step}",
|
|
224
|
-
"idempotency_key": f"{step_prefix}_{step}",
|
|
225
|
-
"timeout_seconds": timeout_seconds,
|
|
226
|
-
}
|
|
227
|
-
out.update(extra)
|
|
228
|
-
return out
|
|
229
|
-
|
|
230
|
-
payee_registration = self.register_agent(payee_agent, **make_step_opts("register_payee"))
|
|
231
|
-
payee_agent_id = payee_registration.get("body", {}).get("agentIdentity", {}).get("agentId")
|
|
232
|
-
_assert_non_empty_string(payee_agent_id, "payee_agent_id")
|
|
233
|
-
|
|
234
|
-
payer_registration = None
|
|
235
|
-
payer_credit = None
|
|
236
|
-
payer_agent_id = None
|
|
237
|
-
payer_agent = params.get("payer_agent")
|
|
238
|
-
if payer_agent is not None:
|
|
239
|
-
if not isinstance(payer_agent, dict):
|
|
240
|
-
raise ValueError("params.payer_agent must be an object")
|
|
241
|
-
_assert_non_empty_string(payer_agent.get("publicKeyPem"), "params.payer_agent.publicKeyPem")
|
|
242
|
-
payer_registration = self.register_agent(payer_agent, **make_step_opts("register_payer"))
|
|
243
|
-
payer_agent_id = payer_registration.get("body", {}).get("agentIdentity", {}).get("agentId")
|
|
244
|
-
_assert_non_empty_string(payer_agent_id, "payer_agent_id")
|
|
245
|
-
|
|
246
|
-
settlement = params.get("settlement") if isinstance(params.get("settlement"), dict) else None
|
|
247
|
-
settlement_amount_cents = settlement.get("amountCents") if settlement else None
|
|
248
|
-
settlement_currency = settlement.get("currency", "USD") if settlement else "USD"
|
|
249
|
-
settlement_payer_agent_id = (
|
|
250
|
-
settlement.get("payerAgentId")
|
|
251
|
-
if settlement and isinstance(settlement.get("payerAgentId"), str)
|
|
252
|
-
else payer_agent_id
|
|
253
|
-
)
|
|
254
|
-
if settlement_amount_cents is not None and settlement_payer_agent_id is None:
|
|
255
|
-
raise ValueError("params.payer_agent or params.settlement.payerAgentId is required when settlement is requested")
|
|
256
|
-
|
|
257
|
-
payer_credit_input = params.get("payer_credit")
|
|
258
|
-
if payer_credit_input is not None:
|
|
259
|
-
if not isinstance(payer_credit_input, dict):
|
|
260
|
-
raise ValueError("params.payer_credit must be an object")
|
|
261
|
-
payer_credit_amount = payer_credit_input.get("amountCents")
|
|
262
|
-
if not isinstance(payer_credit_amount, (int, float)) or payer_credit_amount <= 0:
|
|
263
|
-
raise ValueError("params.payer_credit.amountCents must be a positive number")
|
|
264
|
-
if not payer_agent_id:
|
|
265
|
-
raise ValueError("params.payer_agent is required when params.payer_credit is provided")
|
|
266
|
-
payer_credit = self.credit_agent_wallet(
|
|
267
|
-
payer_agent_id,
|
|
268
|
-
{
|
|
269
|
-
"amountCents": int(payer_credit_amount),
|
|
270
|
-
"currency": payer_credit_input.get("currency", settlement_currency),
|
|
271
|
-
},
|
|
272
|
-
**make_step_opts("credit_payer_wallet"),
|
|
273
|
-
)
|
|
274
|
-
|
|
275
|
-
run_body = dict(params.get("run") or {})
|
|
276
|
-
if settlement_amount_cents is not None:
|
|
277
|
-
if not isinstance(settlement_amount_cents, (int, float)) or settlement_amount_cents <= 0:
|
|
278
|
-
raise ValueError("params.settlement.amountCents must be a positive number")
|
|
279
|
-
run_body["settlement"] = {
|
|
280
|
-
"payerAgentId": settlement_payer_agent_id,
|
|
281
|
-
"amountCents": int(settlement_amount_cents),
|
|
282
|
-
"currency": settlement_currency,
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
run_created = self.create_agent_run(payee_agent_id, run_body, **make_step_opts("create_run"))
|
|
286
|
-
run_id = run_created.get("body", {}).get("run", {}).get("runId")
|
|
287
|
-
_assert_non_empty_string(run_id, "run_id")
|
|
288
|
-
prev_chain_hash = run_created.get("body", {}).get("run", {}).get("lastChainHash")
|
|
289
|
-
_assert_non_empty_string(prev_chain_hash, "run_created.body.run.lastChainHash")
|
|
290
|
-
|
|
291
|
-
actor = params.get("actor") or {"type": "agent", "id": payee_agent_id}
|
|
292
|
-
started_payload = params.get("started_payload") or {"startedBy": "sdk.first_verified_run"}
|
|
293
|
-
run_started = self.append_agent_run_event(
|
|
294
|
-
payee_agent_id,
|
|
295
|
-
run_id,
|
|
296
|
-
{"type": "RUN_STARTED", "actor": actor, "payload": started_payload},
|
|
297
|
-
expected_prev_chain_hash=prev_chain_hash,
|
|
298
|
-
**make_step_opts("run_started"),
|
|
299
|
-
)
|
|
300
|
-
prev_chain_hash = run_started.get("body", {}).get("run", {}).get("lastChainHash")
|
|
301
|
-
_assert_non_empty_string(prev_chain_hash, "run_started.body.run.lastChainHash")
|
|
302
|
-
|
|
303
|
-
evidence_ref = params.get("evidence_ref") if isinstance(params.get("evidence_ref"), str) and params.get("evidence_ref").strip() else f"evidence://{run_id}/output.json"
|
|
304
|
-
evidence_payload = params.get("evidence_payload") if isinstance(params.get("evidence_payload"), dict) else {"evidenceRef": evidence_ref}
|
|
305
|
-
run_evidence_added = self.append_agent_run_event(
|
|
306
|
-
payee_agent_id,
|
|
307
|
-
run_id,
|
|
308
|
-
{"type": "EVIDENCE_ADDED", "actor": actor, "payload": evidence_payload},
|
|
309
|
-
expected_prev_chain_hash=prev_chain_hash,
|
|
310
|
-
**make_step_opts("evidence_added"),
|
|
311
|
-
)
|
|
312
|
-
prev_chain_hash = run_evidence_added.get("body", {}).get("run", {}).get("lastChainHash")
|
|
313
|
-
_assert_non_empty_string(prev_chain_hash, "run_evidence_added.body.run.lastChainHash")
|
|
314
|
-
|
|
315
|
-
completed_payload = dict(params.get("completed_payload") or {})
|
|
316
|
-
output_ref = params.get("output_ref") if isinstance(params.get("output_ref"), str) and params.get("output_ref").strip() else evidence_ref
|
|
317
|
-
completed_payload["outputRef"] = output_ref
|
|
318
|
-
if isinstance(params.get("completed_metrics"), dict):
|
|
319
|
-
completed_payload["metrics"] = params.get("completed_metrics")
|
|
320
|
-
run_completed = self.append_agent_run_event(
|
|
321
|
-
payee_agent_id,
|
|
322
|
-
run_id,
|
|
323
|
-
{"type": "RUN_COMPLETED", "actor": actor, "payload": completed_payload},
|
|
324
|
-
expected_prev_chain_hash=prev_chain_hash,
|
|
325
|
-
**make_step_opts("run_completed"),
|
|
326
|
-
)
|
|
327
|
-
|
|
328
|
-
run = self.get_agent_run(payee_agent_id, run_id, **make_step_opts("get_run"))
|
|
329
|
-
verification = self.get_run_verification(run_id, **make_step_opts("get_verification"))
|
|
330
|
-
settlement_out = None
|
|
331
|
-
if run_body.get("settlement") or run_created.get("body", {}).get("settlement") or run_completed.get("body", {}).get("settlement"):
|
|
332
|
-
settlement_out = self.get_run_settlement(run_id, **make_step_opts("get_settlement"))
|
|
333
|
-
|
|
334
|
-
return {
|
|
335
|
-
"ids": {"run_id": run_id, "payee_agent_id": payee_agent_id, "payer_agent_id": payer_agent_id},
|
|
336
|
-
"payee_registration": payee_registration,
|
|
337
|
-
"payer_registration": payer_registration,
|
|
338
|
-
"payer_credit": payer_credit,
|
|
339
|
-
"run_created": run_created,
|
|
340
|
-
"run_started": run_started,
|
|
341
|
-
"run_evidence_added": run_evidence_added,
|
|
342
|
-
"run_completed": run_completed,
|
|
343
|
-
"run": run,
|
|
344
|
-
"verification": verification,
|
|
345
|
-
"settlement": settlement_out,
|
|
346
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: settld-api-sdk-python
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: Settld API SDK (Python)
|
|
5
|
-
Author: Settld
|
|
6
|
-
License: UNLICENSED
|
|
7
|
-
Requires-Python: >=3.9
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
|
|
10
|
-
# Settld API SDK (Python)
|
|
11
|
-
|
|
12
|
-
Python client for Settld API endpoints, including a high-level `first_verified_run` helper.
|
|
13
|
-
|
|
14
|
-
Quickstart docs live in `docs/QUICKSTART_SDK_PYTHON.md` at repo root.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|