kobil-agent-audit 1.2.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.
@@ -0,0 +1,9 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ .mypy_cache/
9
+ .ruff_cache/
@@ -0,0 +1,128 @@
1
+ KOBIL Agent Trust SDK License
2
+ Version 1.0
3
+
4
+ Copyright (c) 2026 KOBIL GmbH. All rights reserved.
5
+ The Software is developed by the KOBIL AI Team.
6
+
7
+ In short: you may install and use the Software free of charge, also in
8
+ commercial products, and ship it inside your own application. You may not
9
+ change it, copy it into other software, or pass it on or sell it on its
10
+ own. The numbered terms below are the license; this summary is not.
11
+
12
+ 1. Definitions
13
+
14
+ "The Software" is the code and documentation in this package.
15
+
16
+ "You" is the person or organization that accepts these terms.
17
+
18
+ "Your company" is the organization you work for, and the organizations
19
+ that control it, that it controls, or that are under common control
20
+ with it.
21
+
22
+ "Your application" is software or a service that you build and that
23
+ does more than give access to the Software. Your application is not a
24
+ work based on the Software just because it uses the Software.
25
+
26
+ 2. Acceptance
27
+
28
+ By installing, copying or using the Software you accept these terms.
29
+ If you do not accept them, do not use the Software.
30
+
31
+ 3. What you may do
32
+
33
+ KOBIL grants you and your company a non-exclusive, worldwide,
34
+ non-transferable license, free of charge, to:
35
+
36
+ a. install and use the Software, unchanged, to build, test and run your
37
+ applications, including commercial ones that you charge for;
38
+ b. make the copies of the Software that this needs, for example on
39
+ developer machines, on build servers and in your application's
40
+ environments;
41
+ c. ship the Software, unchanged, as part of your application, for
42
+ example inside its container image, package or bundle.
43
+
44
+ Your company's employees and contractors may do the same on your
45
+ company's behalf.
46
+
47
+ Building your application with tools such as bundlers, minifiers,
48
+ transpilers or compilers is not a change to the Software. Using AI
49
+ coding tools to write applications that use the Software is allowed.
50
+
51
+ 4. Shipping the Software inside your application
52
+
53
+ When you ship the Software as part of your application:
54
+
55
+ a. the Software must be needed for your application to work, and must
56
+ not be offered as a library, SDK or tool of its own;
57
+ b. the people who receive your application get no right to use the
58
+ Software apart from your application;
59
+ c. keep this license and the copyright notice with the Software, or in
60
+ your application's third-party notices.
61
+
62
+ 5. What you may not do
63
+
64
+ You may not:
65
+
66
+ a. change, adapt or translate the Software, or make works based on it;
67
+ b. copy the Software's code, or substantial parts of it, into other
68
+ software, whether by hand or with automated or AI tools, or use the
69
+ Software to train AI models;
70
+ c. publish, share, sublicense, sell, rent or lease the Software on its
71
+ own, or as part of another SDK, library, framework or developer
72
+ tool;
73
+ d. remove or change this license or any copyright notice in the
74
+ Software;
75
+ e. use the Software to break the law, or to suggest that KOBIL endorses
76
+ your application.
77
+
78
+ These limits apply to the Software itself. You may sell and
79
+ commercially distribute your application.
80
+
81
+ 6. KOBIL services
82
+
83
+ The Software can connect to KOBIL services. Using those services is
84
+ governed by their own terms, not by this license. This license does not
85
+ give you access to any KOBIL service.
86
+
87
+ 7. Ownership
88
+
89
+ KOBIL keeps all rights in the Software that this license does not
90
+ expressly grant. This license gives you no right to KOBIL's names,
91
+ logos or trademarks, except to say truthfully that your application
92
+ uses the Software.
93
+
94
+ 8. Other software
95
+
96
+ The Software uses third-party open-source packages. Each of them is
97
+ covered by its own license, not by this one.
98
+
99
+ 9. New versions
100
+
101
+ KOBIL may release new versions of the Software under a new version of
102
+ this license. Each copy of the Software is covered by the license that
103
+ came with it.
104
+
105
+ 10. Violations
106
+
107
+ If you break these terms, KOBIL may tell you so in writing. The first
108
+ time this happens, your license continues if, within 30 days of that
109
+ notice, you fully comply again and take practical steps to correct past
110
+ violations. Otherwise, and after any later violation, your license
111
+ ends. You must then stop using and shipping the Software.
112
+
113
+ 11. No warranty
114
+
115
+ As far as the law allows, the Software comes "as is", without any
116
+ warranty or condition, express or implied, including fitness for a
117
+ particular purpose and non-infringement.
118
+
119
+ 12. Limitation of liability
120
+
121
+ As far as the law allows, KOBIL is not liable for any damages that come
122
+ from these terms or from using, or not being able to use, the Software,
123
+ under any kind of legal claim.
124
+
125
+ 13. Other permissions
126
+
127
+ For any use these terms do not allow, ask KOBIL for written permission
128
+ at www.kobil.com.
@@ -0,0 +1,58 @@
1
+ Metadata-Version: 2.5
2
+ Name: kobil-agent-audit
3
+ Version: 1.2.0
4
+ Summary: Audit, Observability & Human-in-the-Loop for the KOBIL AI Trust Platform
5
+ License-Expression: LicenseRef-KOBIL-Agent-Trust-SDK-1.0
6
+ License-File: LICENSE
7
+ Keywords: agent,ai-security,audit,ciba,compliance,human-in-the-loop,kobil,observability,siem,token-tracking
8
+ Requires-Python: >=3.10
9
+ Requires-Dist: httpx>=0.27
10
+ Provides-Extra: dev
11
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
12
+ Requires-Dist: pytest>=8.0; extra == 'dev'
13
+ Requires-Dist: respx>=0.21; extra == 'dev'
14
+ Description-Content-Type: text/markdown
15
+
16
+ # kobil-agent-audit (Python)
17
+
18
+ Python mirror of the `@uveysservetoglu/kobil-agent-audit` TypeScript SDK — audit,
19
+ observability, and human-in-the-loop for the KOBIL AI Trust Platform.
20
+
21
+ Behavioral 1:1 port of the TypeScript package in `../src`. The TypeScript
22
+ source is the reference specification; the Python code reproduces its logic,
23
+ HTTP wire format, and error semantics. `AuditConfig` is a snake_case dataclass
24
+ (developer surface); the entity types (AuditEvent, TokenTrackingEntry, …) are
25
+ camelCase dicts because they are the literal JSON exchanged with the platform.
26
+
27
+ ## Install (dev)
28
+
29
+ ```bash
30
+ python -m venv .venv && source .venv/bin/activate
31
+ pip install -e ".[dev]"
32
+ ```
33
+
34
+ ## Test
35
+
36
+ ```bash
37
+ pytest
38
+ ```
39
+
40
+ ## Usage
41
+
42
+ ```python
43
+ import asyncio
44
+ from kobil_agent_audit import KobilAgentAudit
45
+
46
+ audit = KobilAgentAudit(
47
+ platform_url="https://platform.kobil.com",
48
+ agent_id="my-agent",
49
+ api_token="tok",
50
+ )
51
+
52
+ audit.log_action("call_tool", "weather-api", "success", {"temp": 72})
53
+
54
+ async def main():
55
+ await audit.destroy() # flushes remaining events
56
+
57
+ asyncio.run(main())
58
+ ```
@@ -0,0 +1,43 @@
1
+ # kobil-agent-audit (Python)
2
+
3
+ Python mirror of the `@uveysservetoglu/kobil-agent-audit` TypeScript SDK — audit,
4
+ observability, and human-in-the-loop for the KOBIL AI Trust Platform.
5
+
6
+ Behavioral 1:1 port of the TypeScript package in `../src`. The TypeScript
7
+ source is the reference specification; the Python code reproduces its logic,
8
+ HTTP wire format, and error semantics. `AuditConfig` is a snake_case dataclass
9
+ (developer surface); the entity types (AuditEvent, TokenTrackingEntry, …) are
10
+ camelCase dicts because they are the literal JSON exchanged with the platform.
11
+
12
+ ## Install (dev)
13
+
14
+ ```bash
15
+ python -m venv .venv && source .venv/bin/activate
16
+ pip install -e ".[dev]"
17
+ ```
18
+
19
+ ## Test
20
+
21
+ ```bash
22
+ pytest
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ```python
28
+ import asyncio
29
+ from kobil_agent_audit import KobilAgentAudit
30
+
31
+ audit = KobilAgentAudit(
32
+ platform_url="https://platform.kobil.com",
33
+ agent_id="my-agent",
34
+ api_token="tok",
35
+ )
36
+
37
+ audit.log_action("call_tool", "weather-api", "success", {"temp": 72})
38
+
39
+ async def main():
40
+ await audit.destroy() # flushes remaining events
41
+
42
+ asyncio.run(main())
43
+ ```
@@ -0,0 +1,211 @@
1
+ """kobil-agent-audit — Audit, Observability & Human-in-the-Loop (Python).
2
+
3
+ Python mirror of the ``@uveysservetoglu/kobil-agent-audit`` TypeScript package.
4
+
5
+ Provides:
6
+ - Batched audit event logging with platform / SIEM / webhook sinks
7
+ - Full token lifecycle tracking with anomaly detection
8
+ - CIBA push notifications for human approval
9
+ - Async authorization gates for tool execution
10
+ - Compliance report generation, export, and scheduling
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import Any, Optional
16
+
17
+ from .async_auth import AsyncAuthError, AsyncAuthorization
18
+ from .ciba_notification import CIBANotificationError, CIBANotificationService
19
+ from .compliance import ComplianceError, ComplianceReporter
20
+ from .logger import AuditLogger, AuditLoggerError, AuditQueryFilter, AuditQueryResult
21
+ from .token_tracker import TokenTracker, TokenTrackerError
22
+ from .types import (
23
+ ALL_EVENT_TYPES,
24
+ AnomalyDetection,
25
+ AnomalySeverity,
26
+ AnomalyType,
27
+ ApprovalStats,
28
+ AsyncAuthRequest,
29
+ AsyncAuthResponse,
30
+ AsyncAuthStatus,
31
+ AuditConfig,
32
+ AuditEvent,
33
+ AuditEventInput,
34
+ AuditOutcome,
35
+ CIBANotification,
36
+ CIBAStatus,
37
+ ComplianceReport,
38
+ EventType,
39
+ SinkType,
40
+ TokenStats,
41
+ TokenStatus,
42
+ TokenTrackingEntry,
43
+ Urgency,
44
+ )
45
+
46
+ # The release pipeline stamps the version into pyproject.toml only, so read it back from the installed metadata.
47
+ from importlib.metadata import PackageNotFoundError as _PackageNotFoundError, version as _version
48
+
49
+ try:
50
+ __version__ = _version("kobil-agent-audit")
51
+ except _PackageNotFoundError: # running from a source checkout that is not installed
52
+ __version__ = "0.0.0"
53
+
54
+
55
+ class AuditError(Exception):
56
+ """Base error class for the audit package."""
57
+
58
+ def __init__(self, message: str, code: str, cause: Any = None) -> None:
59
+ super().__init__(message)
60
+ self.message = message
61
+ self.code = code
62
+ self.cause = cause
63
+ self.name = "AuditError"
64
+
65
+
66
+ class KobilAgentAudit:
67
+ """Top-level entry point — instantiates and exposes all sub-systems."""
68
+
69
+ def __init__(
70
+ self,
71
+ platform_url: Optional[str] = None,
72
+ agent_id: Optional[str] = None,
73
+ api_token: str = "",
74
+ sink_type: SinkType = "platform",
75
+ sink_url: Optional[str] = None,
76
+ batch_size: int = 50,
77
+ flush_interval_ms: int = 10_000,
78
+ cert_path: Optional[str] = None,
79
+ key_path: Optional[str] = None,
80
+ timeout_ms: int = 30_000,
81
+ ) -> None:
82
+ if not platform_url:
83
+ raise AuditError("AuditConfig.platformUrl is required", "CONFIG_MISSING")
84
+ if not agent_id:
85
+ raise AuditError("AuditConfig.agentId is required", "CONFIG_MISSING")
86
+
87
+ self.config = AuditConfig(
88
+ platform_url=platform_url,
89
+ agent_id=agent_id,
90
+ api_token=api_token,
91
+ sink_type=sink_type,
92
+ sink_url=sink_url,
93
+ batch_size=batch_size,
94
+ flush_interval_ms=flush_interval_ms,
95
+ cert_path=cert_path,
96
+ key_path=key_path,
97
+ timeout_ms=timeout_ms,
98
+ )
99
+ self.logger = AuditLogger(self.config)
100
+ self.token_tracker = TokenTracker(self.config)
101
+ self.notifications = CIBANotificationService(self.config)
102
+ self.async_auth = AsyncAuthorization(self.config)
103
+ self.compliance = ComplianceReporter(self.config)
104
+
105
+ def log_action(
106
+ self,
107
+ action: str,
108
+ resource: str,
109
+ outcome: AuditOutcome,
110
+ metadata: Optional[dict] = None,
111
+ ) -> None:
112
+ """Convenience helper to log a single tool_call action event."""
113
+ event: dict = {
114
+ "agentId": self.config.agent_id,
115
+ "eventType": "tool_call",
116
+ "action": action,
117
+ "resource": resource,
118
+ "outcome": outcome,
119
+ }
120
+ if metadata is not None:
121
+ event["metadata"] = metadata
122
+ self.logger.log(event)
123
+
124
+ async def log_event(self, input: "AuditEventInput") -> None:
125
+ """Log a single arbitrary audit event and flush immediately.
126
+
127
+ Mirrors the TS ``KobilAgentAudit.logEvent`` facade added in 0.2.16 —
128
+ callers use this instead of hand-rolling ``POST /v1/events`` so the
129
+ SDK owns transport (incl. mTLS) and endpoint resolution. The immediate
130
+ flush gives fire-and-forget parity with pre-batched inline emit paths.
131
+ """
132
+ event: dict = {
133
+ "agentId": input.get("agentId") or self.config.agent_id,
134
+ "eventType": input.get("eventType"),
135
+ "action": input.get("action"),
136
+ "resource": input.get("resource") or input.get("resourceType") or "",
137
+ "outcome": input.get("outcome", "success"),
138
+ }
139
+ # Top-level optional fields — mirror TS widening.
140
+ for key in ("resourceType", "resourceId", "correlationId", "actorId", "userId", "details"):
141
+ if input.get(key) is not None:
142
+ event[key] = input[key]
143
+ # ``metadata`` falls back to ``details`` when the caller only provided the latter.
144
+ metadata = input.get("metadata")
145
+ if metadata is None:
146
+ metadata = input.get("details")
147
+ if metadata is not None:
148
+ event["metadata"] = metadata
149
+ self.logger.log(event)
150
+ await self.logger.flush()
151
+
152
+ async def destroy(self) -> None:
153
+ """Gracefully tear down all sub-systems."""
154
+ errors: list[Exception] = []
155
+ for fn in (
156
+ self.logger.destroy,
157
+ self.token_tracker.destroy,
158
+ self.notifications.destroy,
159
+ self.async_auth.destroy,
160
+ self.compliance.destroy,
161
+ ):
162
+ try:
163
+ result = fn()
164
+ if hasattr(result, "__await__"):
165
+ await result
166
+ except Exception as e: # noqa: BLE001
167
+ errors.append(e if isinstance(e, Exception) else Exception(str(e)))
168
+ if errors:
169
+ msg = "; ".join(str(e) for e in errors)
170
+ raise AuditError(
171
+ f"Audit destroy encountered {len(errors)} error(s): {msg}", "DESTROY_ERROR"
172
+ )
173
+
174
+
175
+ __all__ = [
176
+ "AuditError",
177
+ "KobilAgentAudit",
178
+ "AuditLogger",
179
+ "AuditLoggerError",
180
+ "AuditQueryFilter",
181
+ "AuditQueryResult",
182
+ "TokenTracker",
183
+ "TokenTrackerError",
184
+ "CIBANotificationService",
185
+ "CIBANotificationError",
186
+ "AsyncAuthorization",
187
+ "AsyncAuthError",
188
+ "ComplianceReporter",
189
+ "ComplianceError",
190
+ "AuditConfig",
191
+ "AuditEvent",
192
+ "AuditEventInput",
193
+ "AuditOutcome",
194
+ "EventType",
195
+ "SinkType",
196
+ "TokenStatus",
197
+ "TokenTrackingEntry",
198
+ "CIBAStatus",
199
+ "CIBANotification",
200
+ "Urgency",
201
+ "AsyncAuthRequest",
202
+ "AsyncAuthResponse",
203
+ "AsyncAuthStatus",
204
+ "TokenStats",
205
+ "ApprovalStats",
206
+ "AnomalyType",
207
+ "AnomalySeverity",
208
+ "AnomalyDetection",
209
+ "ComplianceReport",
210
+ "ALL_EVENT_TYPES",
211
+ ]
@@ -0,0 +1,38 @@
1
+ """Shared helpers for the kobil-agent-audit Python package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import uuid as _uuid
6
+ from datetime import datetime, timezone
7
+
8
+
9
+ def uuid() -> str:
10
+ """Mirror of crypto.randomUUID()."""
11
+ return str(_uuid.uuid4())
12
+
13
+
14
+ def iso_now() -> str:
15
+ """Mirror of ``new Date().toISOString()`` — always millisecond + 'Z'."""
16
+ dt = datetime.now(timezone.utc)
17
+ return dt.strftime("%Y-%m-%dT%H:%M:%S.") + f"{dt.microsecond // 1000:03d}Z"
18
+
19
+
20
+ def iso_from_dt(dt: datetime) -> str:
21
+ """Mirror of ``date.toISOString()`` for an arbitrary datetime."""
22
+ if dt.tzinfo is None:
23
+ dt = dt.replace(tzinfo=timezone.utc)
24
+ dt = dt.astimezone(timezone.utc)
25
+ return dt.strftime("%Y-%m-%dT%H:%M:%S.") + f"{dt.microsecond // 1000:03d}Z"
26
+
27
+
28
+ def parse_iso_ms(value: str) -> int:
29
+ """Parse an ISO-8601 string to epoch milliseconds (mirror Date.parse)."""
30
+ normalized = value.replace("Z", "+00:00")
31
+ dt = datetime.fromisoformat(normalized)
32
+ if dt.tzinfo is None:
33
+ dt = dt.replace(tzinfo=timezone.utc)
34
+ return int(dt.timestamp() * 1000)
35
+
36
+
37
+ def now_ms() -> int:
38
+ return int(datetime.now(timezone.utc).timestamp() * 1000)
@@ -0,0 +1,163 @@
1
+ """AsyncAuthorization — human-in-the-loop approval gates (Python mirror).
2
+
3
+ Mirror of ``kobil-agent-audit/src/async-auth.ts``.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import asyncio
9
+ import json
10
+ from typing import Any, Awaitable, Callable
11
+ from urllib.parse import quote
12
+
13
+ import httpx
14
+
15
+ from ._common import iso_now, now_ms, uuid
16
+ from .types import AsyncAuthRequest, AsyncAuthResponse, AuditConfig
17
+
18
+
19
+ class AsyncAuthError(Exception):
20
+ def __init__(self, message: str, code: str, cause: Any = None) -> None:
21
+ super().__init__(message)
22
+ self.message = message
23
+ self.code = code
24
+ self.cause = cause
25
+ self.name = "AsyncAuthError"
26
+
27
+
28
+ class AsyncAuthorization:
29
+ def __init__(self, config: AuditConfig) -> None:
30
+ self.config = config
31
+ self._requests: dict[str, AsyncAuthResponse] = {}
32
+
33
+ def destroy(self) -> None:
34
+ self._requests.clear()
35
+
36
+ # ------------------------------------------------------------------
37
+ # Public API
38
+ # ------------------------------------------------------------------
39
+
40
+ async def request_approval(self, request: AsyncAuthRequest) -> AsyncAuthResponse:
41
+ request_id = uuid()
42
+ full: AsyncAuthRequest = {**request, "requestId": request_id}
43
+ response = await self._submit_remote(full)
44
+ self._requests[request_id] = response
45
+ return response
46
+
47
+ async def check_approval(self, request_id: str) -> AsyncAuthResponse:
48
+ try:
49
+ remote = await self._fetch_remote_status(request_id)
50
+ self._requests[request_id] = remote
51
+ return remote
52
+ except AsyncAuthError:
53
+ raise
54
+ except Exception:
55
+ local = self._requests.get(request_id)
56
+ if not local:
57
+ raise AsyncAuthError(
58
+ f"Approval request not found: {request_id}", "REQUEST_NOT_FOUND"
59
+ )
60
+ return dict(local) # type: ignore[return-value]
61
+
62
+ async def wait_for_approval(
63
+ self, request_id: str, timeout_ms: int = 300_000
64
+ ) -> AsyncAuthResponse:
65
+ deadline = now_ms() + timeout_ms
66
+ poll_interval_ms = 2000
67
+
68
+ while now_ms() < deadline:
69
+ response = await self.check_approval(request_id)
70
+ if response.get("status") != "pending":
71
+ return response
72
+ await asyncio.sleep(min(poll_interval_ms, deadline - now_ms()) / 1000)
73
+
74
+ expired: AsyncAuthResponse = {
75
+ "requestId": request_id,
76
+ "status": "expired",
77
+ "respondedAt": iso_now(),
78
+ }
79
+ self._requests[request_id] = expired
80
+ return expired
81
+
82
+ async def cancel_request(self, request_id: str) -> None:
83
+ url = f"{self.config.platform_url}/v1/async-auth/requests/{quote(request_id, safe='')}"
84
+ async with httpx.AsyncClient() as http:
85
+ res = await http.request(
86
+ "DELETE",
87
+ url,
88
+ headers={
89
+ "Authorization": f"Bearer {self.config.api_token}",
90
+ "X-Agent-Id": self.config.agent_id,
91
+ },
92
+ )
93
+ if not res.is_success:
94
+ raise AsyncAuthError(
95
+ f"Failed to cancel request: {res.status_code} — {res.text}", "CANCEL_ERROR"
96
+ )
97
+ self._requests.pop(request_id, None)
98
+
99
+ def wrap_with_approval(
100
+ self,
101
+ action: str,
102
+ tool_fn: Callable[..., Awaitable[Any]],
103
+ ) -> Callable[..., Awaitable[Any]]:
104
+ async def wrapped(*args: Any, **kwargs: Any) -> Any:
105
+ response = await self.request_approval(
106
+ {
107
+ "agentId": self.config.agent_id,
108
+ "userId": "system",
109
+ "action": action,
110
+ "reason": f"Agent {self.config.agent_id} wants to execute: {action}",
111
+ "urgency": "normal",
112
+ "timeout": 300,
113
+ }
114
+ )
115
+ resolved = await self.wait_for_approval(response["requestId"], 300_000)
116
+ if resolved.get("status") != "approved":
117
+ raise AsyncAuthError(
118
+ f'Approval {resolved.get("status")} for action "{action}" '
119
+ f'(request {resolved.get("requestId")})',
120
+ "APPROVAL_DENIED",
121
+ )
122
+ return await tool_fn(*args, **kwargs)
123
+
124
+ return wrapped
125
+
126
+ # ------------------------------------------------------------------
127
+ # Remote helpers
128
+ # ------------------------------------------------------------------
129
+
130
+ async def _submit_remote(self, request: AsyncAuthRequest) -> AsyncAuthResponse:
131
+ url = f"{self.config.platform_url}/v1/async-auth/requests"
132
+ async with httpx.AsyncClient() as http:
133
+ res = await http.post(
134
+ url,
135
+ headers={
136
+ "Content-Type": "application/json",
137
+ "Authorization": f"Bearer {self.config.api_token}",
138
+ "X-Agent-Id": self.config.agent_id,
139
+ },
140
+ content=json.dumps(request, separators=(",", ":")),
141
+ )
142
+ if not res.is_success:
143
+ raise AsyncAuthError(
144
+ f"Failed to submit approval request: {res.status_code} — {res.text}", "SUBMIT_ERROR"
145
+ )
146
+ return res.json()
147
+
148
+ async def _fetch_remote_status(self, request_id: str) -> AsyncAuthResponse:
149
+ url = f"{self.config.platform_url}/v1/async-auth/requests/{quote(request_id, safe='')}"
150
+ async with httpx.AsyncClient() as http:
151
+ res = await http.get(
152
+ url,
153
+ headers={
154
+ "Authorization": f"Bearer {self.config.api_token}",
155
+ "X-Agent-Id": self.config.agent_id,
156
+ },
157
+ )
158
+ if not res.is_success:
159
+ raise AsyncAuthError(f"Remote status check failed: {res.status_code}", "STATUS_FETCH_ERROR")
160
+ return res.json()
161
+
162
+
163
+ __all__ = ["AsyncAuthorization", "AsyncAuthError"]