kobil-agent-sdk 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.
Files changed (33) hide show
  1. kobil_agent_sdk-1.2.0/.gitignore +9 -0
  2. kobil_agent_sdk-1.2.0/LICENSE +128 -0
  3. kobil_agent_sdk-1.2.0/PKG-INFO +64 -0
  4. kobil_agent_sdk-1.2.0/README.md +32 -0
  5. kobil_agent_sdk-1.2.0/kobil_agent_sdk/__init__.py +120 -0
  6. kobil_agent_sdk-1.2.0/kobil_agent_sdk/auth/__init__.py +23 -0
  7. kobil_agent_sdk-1.2.0/kobil_agent_sdk/auth/ciba.py +207 -0
  8. kobil_agent_sdk-1.2.0/kobil_agent_sdk/auth/oauth.py +450 -0
  9. kobil_agent_sdk-1.2.0/kobil_agent_sdk/auth/server_approval.py +264 -0
  10. kobil_agent_sdk-1.2.0/kobil_agent_sdk/auth/vault_compat.py +213 -0
  11. kobil_agent_sdk-1.2.0/kobil_agent_sdk/config.py +221 -0
  12. kobil_agent_sdk-1.2.0/kobil_agent_sdk/easy.py +303 -0
  13. kobil_agent_sdk-1.2.0/kobil_agent_sdk/fga.py +178 -0
  14. kobil_agent_sdk-1.2.0/kobil_agent_sdk/identity/__init__.py +3 -0
  15. kobil_agent_sdk-1.2.0/kobil_agent_sdk/identity/agent_identity.py +211 -0
  16. kobil_agent_sdk-1.2.0/kobil_agent_sdk/integrations/__init__.py +81 -0
  17. kobil_agent_sdk-1.2.0/kobil_agent_sdk/integrations/anthropic.py +90 -0
  18. kobil_agent_sdk-1.2.0/kobil_agent_sdk/integrations/claude_agent.py +157 -0
  19. kobil_agent_sdk-1.2.0/kobil_agent_sdk/integrations/google_adk.py +142 -0
  20. kobil_agent_sdk-1.2.0/kobil_agent_sdk/integrations/google_genai.py +88 -0
  21. kobil_agent_sdk-1.2.0/kobil_agent_sdk/integrations/openai.py +100 -0
  22. kobil_agent_sdk-1.2.0/kobil_agent_sdk/integrations/openai_agents.py +157 -0
  23. kobil_agent_sdk-1.2.0/kobil_agent_sdk/kobil_ai.py +281 -0
  24. kobil_agent_sdk-1.2.0/kobil_agent_sdk/registry.py +146 -0
  25. kobil_agent_sdk-1.2.0/kobil_agent_sdk/types.py +204 -0
  26. kobil_agent_sdk-1.2.0/kobil_agent_sdk/web/__init__.py +21 -0
  27. kobil_agent_sdk-1.2.0/kobil_agent_sdk/web/identity.py +277 -0
  28. kobil_agent_sdk-1.2.0/kobil_agent_sdk/wrappers/__init__.py +13 -0
  29. kobil_agent_sdk-1.2.0/kobil_agent_sdk/wrappers/anthropic.py +70 -0
  30. kobil_agent_sdk-1.2.0/kobil_agent_sdk/wrappers/google_adk.py +45 -0
  31. kobil_agent_sdk-1.2.0/kobil_agent_sdk/wrappers/google_genai.py +95 -0
  32. kobil_agent_sdk-1.2.0/kobil_agent_sdk/wrappers/openai.py +90 -0
  33. kobil_agent_sdk-1.2.0/pyproject.toml +52 -0
@@ -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,64 @@
1
+ Metadata-Version: 2.5
2
+ Name: kobil-agent-sdk
3
+ Version: 1.2.0
4
+ Summary: KOBIL AI Trust Platform Agent SDK — OAuth 2.1, CIBA, DPoP, agent identity, and framework integrations
5
+ License-Expression: LicenseRef-KOBIL-Agent-Trust-SDK-1.0
6
+ License-File: LICENSE
7
+ Keywords: agent,ai-trust,ciba,dpop,kobil,oauth,sdk
8
+ Requires-Python: >=3.10
9
+ Requires-Dist: cryptography>=42.0
10
+ Requires-Dist: httpx>=0.27
11
+ Provides-Extra: all
12
+ Requires-Dist: anthropic>=0.20; extra == 'all'
13
+ Requires-Dist: claude-agent-sdk>=0.1; extra == 'all'
14
+ Requires-Dist: google-adk>=2.10; extra == 'all'
15
+ Requires-Dist: google-genai>=0.1; extra == 'all'
16
+ Requires-Dist: openai-agents>=0.1; extra == 'all'
17
+ Requires-Dist: openai>=1.0; extra == 'all'
18
+ Provides-Extra: anthropic
19
+ Requires-Dist: anthropic>=0.20; extra == 'anthropic'
20
+ Requires-Dist: claude-agent-sdk>=0.1; extra == 'anthropic'
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
23
+ Requires-Dist: pytest>=8.0; extra == 'dev'
24
+ Requires-Dist: respx>=0.21; extra == 'dev'
25
+ Provides-Extra: google
26
+ Requires-Dist: google-adk>=2.10; extra == 'google'
27
+ Requires-Dist: google-genai>=0.1; extra == 'google'
28
+ Provides-Extra: openai
29
+ Requires-Dist: openai-agents>=0.1; extra == 'openai'
30
+ Requires-Dist: openai>=1.0; extra == 'openai'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # kobil-agent-sdk (Python)
34
+
35
+ Python mirror of the `@uveysservetoglu/kobil-agent-sdk` TypeScript SDK.
36
+
37
+ This is a behavioral 1:1 port of the TypeScript package in `../src`, delivered
38
+ in layers given its size (~11k LOC). The **current layer** is the self-contained
39
+ auth foundation:
40
+
41
+ - **config** — `KobilSDKConfig`, `create_config` (endpoint derivation, defaults)
42
+ - **OAuth 2.1** — PKCE (`generate_code_verifier`/`generate_code_challenge`),
43
+ authorization-code exchange, client-credentials via the Management API proxy
44
+ (mTLS), RFC 8693 token exchange, refresh; automatic retry with backoff
45
+ - **CIBA** — initiate / poll / status
46
+ - **DPoP** — ECDSA P-256 (ES256), emitted in IEEE-P1363 raw `R||S` form to match
47
+ WebCrypto so proofs are byte-compatible with the TypeScript SDK
48
+
49
+ Subsequent layers add `KobilAI`, the tool registry, agent identity, platform,
50
+ the isolated key-signer subprocess, and the vendor framework adapters
51
+ (OpenAI / Anthropic / Google).
52
+
53
+ ## Install (dev)
54
+
55
+ ```bash
56
+ python -m venv .venv && source .venv/bin/activate
57
+ pip install -e ".[dev]"
58
+ ```
59
+
60
+ ## Test
61
+
62
+ ```bash
63
+ pytest
64
+ ```
@@ -0,0 +1,32 @@
1
+ # kobil-agent-sdk (Python)
2
+
3
+ Python mirror of the `@uveysservetoglu/kobil-agent-sdk` TypeScript SDK.
4
+
5
+ This is a behavioral 1:1 port of the TypeScript package in `../src`, delivered
6
+ in layers given its size (~11k LOC). The **current layer** is the self-contained
7
+ auth foundation:
8
+
9
+ - **config** — `KobilSDKConfig`, `create_config` (endpoint derivation, defaults)
10
+ - **OAuth 2.1** — PKCE (`generate_code_verifier`/`generate_code_challenge`),
11
+ authorization-code exchange, client-credentials via the Management API proxy
12
+ (mTLS), RFC 8693 token exchange, refresh; automatic retry with backoff
13
+ - **CIBA** — initiate / poll / status
14
+ - **DPoP** — ECDSA P-256 (ES256), emitted in IEEE-P1363 raw `R||S` form to match
15
+ WebCrypto so proofs are byte-compatible with the TypeScript SDK
16
+
17
+ Subsequent layers add `KobilAI`, the tool registry, agent identity, platform,
18
+ the isolated key-signer subprocess, and the vendor framework adapters
19
+ (OpenAI / Anthropic / Google).
20
+
21
+ ## Install (dev)
22
+
23
+ ```bash
24
+ python -m venv .venv && source .venv/bin/activate
25
+ pip install -e ".[dev]"
26
+ ```
27
+
28
+ ## Test
29
+
30
+ ```bash
31
+ pytest
32
+ ```
@@ -0,0 +1,120 @@
1
+ """kobil-agent-sdk — KOBIL AI Trust Platform Agent SDK (Python).
2
+
3
+ Python mirror of the ``@uveysservetoglu/kobil-agent-sdk`` TypeScript package.
4
+
5
+ This package is being ported in layers. The current layer covers the
6
+ self-contained **auth foundation**: configuration, OAuth 2.1 (PKCE, client
7
+ credentials via the Management API proxy/mTLS, token exchange, refresh),
8
+ CIBA, FGA, and DPoP (ECDSA P-256 / ES256). The ``KobilAI`` facade, registry,
9
+ identity, platform, key-signer isolation, and vendor adapters follow in
10
+ subsequent layers.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from .auth import (
16
+ CIBAClient,
17
+ DPoPManager,
18
+ OAuthClient,
19
+ TokenExchangeClient,
20
+ TokenExchangeError,
21
+ TokenExchangeRequest,
22
+ TokenExchangeResponse,
23
+ generate_code_challenge,
24
+ generate_code_verifier,
25
+ )
26
+ from .config import KobilSDKConfig, create_config
27
+ from .easy import (
28
+ KobilToolContext,
29
+ KobilToolOptions,
30
+ SecureOptions,
31
+ UnifiedConfig,
32
+ create_kobil,
33
+ extract_tool_context,
34
+ kobil_tool,
35
+ secure,
36
+ )
37
+ from .fga import FGAClient, FGAClientConfig
38
+ from .identity.agent_identity import AgentIdentityManager
39
+ from .kobil_ai import KobilAI
40
+ from .registry import ToolRegistry
41
+ from .types import (
42
+ AgentIdentity,
43
+ AuthorizationDeniedError,
44
+ AuthorizationTimeoutError,
45
+ AuthResult,
46
+ CIBAAuthRequest,
47
+ CIBAAuthResponse,
48
+ ConfigurationError,
49
+ FGAError,
50
+ FGACheckRequest,
51
+ FGACheckResponse,
52
+ KobilConfig,
53
+ KobilSDKError,
54
+ OAuthError,
55
+ ToolDescriptor,
56
+ )
57
+ from .web import (
58
+ KobilIdentity,
59
+ KobilIdentityOptions,
60
+ KobilUser,
61
+ create_kobil_identity,
62
+ )
63
+
64
+ # The release pipeline stamps the version into pyproject.toml only, so read it back from the installed metadata.
65
+ from importlib.metadata import PackageNotFoundError as _PackageNotFoundError, version as _version
66
+
67
+ try:
68
+ __version__ = _version("kobil-agent-sdk")
69
+ except _PackageNotFoundError: # running from a source checkout that is not installed
70
+ __version__ = "0.0.0"
71
+
72
+ __all__ = [
73
+ # facade + core
74
+ "KobilAI",
75
+ "ToolRegistry",
76
+ "AgentIdentityManager",
77
+ "KobilToolContext",
78
+ "KobilToolOptions",
79
+ "SecureOptions",
80
+ "UnifiedConfig",
81
+ "kobil_tool",
82
+ "secure",
83
+ "create_kobil",
84
+ "extract_tool_context",
85
+ "FGAClient",
86
+ "FGAClientConfig",
87
+ # config
88
+ "KobilSDKConfig",
89
+ "create_config",
90
+ # auth
91
+ "OAuthClient",
92
+ "CIBAClient",
93
+ "DPoPManager",
94
+ "TokenExchangeClient",
95
+ "TokenExchangeError",
96
+ "TokenExchangeRequest",
97
+ "TokenExchangeResponse",
98
+ "generate_code_verifier",
99
+ "generate_code_challenge",
100
+ # types
101
+ "KobilConfig",
102
+ "AuthResult",
103
+ "AgentIdentity",
104
+ "CIBAAuthRequest",
105
+ "CIBAAuthResponse",
106
+ "FGACheckRequest",
107
+ "FGACheckResponse",
108
+ "ToolDescriptor",
109
+ "KobilSDKError",
110
+ "OAuthError",
111
+ "ConfigurationError",
112
+ "FGAError",
113
+ "AuthorizationTimeoutError",
114
+ "AuthorizationDeniedError",
115
+ # web identity (OIDC login for MiniApp / web agents)
116
+ "create_kobil_identity",
117
+ "KobilIdentity",
118
+ "KobilIdentityOptions",
119
+ "KobilUser",
120
+ ]
@@ -0,0 +1,23 @@
1
+ """Auth subpackage — OAuth, CIBA, DPoP, token exchange."""
2
+
3
+ from .ciba import CIBAClient
4
+ from .oauth import OAuthClient, generate_code_challenge, generate_code_verifier
5
+ from .vault_compat import (
6
+ DPoPManager,
7
+ TokenExchangeClient,
8
+ TokenExchangeError,
9
+ TokenExchangeRequest,
10
+ TokenExchangeResponse,
11
+ )
12
+
13
+ __all__ = [
14
+ "CIBAClient",
15
+ "OAuthClient",
16
+ "generate_code_verifier",
17
+ "generate_code_challenge",
18
+ "DPoPManager",
19
+ "TokenExchangeClient",
20
+ "TokenExchangeError",
21
+ "TokenExchangeRequest",
22
+ "TokenExchangeResponse",
23
+ ]
@@ -0,0 +1,207 @@
1
+ """CIBA client (mirror of src/auth/ciba.ts)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from typing import Optional
7
+
8
+ import httpx
9
+
10
+ from ..config import KobilSDKConfig
11
+ from ..types import (
12
+ AuthorizationDeniedError,
13
+ AuthorizationTimeoutError,
14
+ AuthResult,
15
+ CIBAAuthRequest,
16
+ CIBAAuthResponse,
17
+ KobilSDKError,
18
+ OAuthError,
19
+ )
20
+
21
+ DEFAULT_POLL_INTERVAL_SECONDS = 5
22
+ MAX_POLL_DURATION_SECONDS = 300
23
+
24
+
25
+ def _now_ms() -> int:
26
+ import time
27
+
28
+ return int(time.time() * 1000)
29
+
30
+
31
+ class CIBAClient:
32
+ def __init__(self, config) -> None:
33
+ self._cfg = KobilSDKConfig(config)
34
+
35
+ async def initiate_auth(self, request: CIBAAuthRequest) -> CIBAAuthResponse:
36
+ # Accept either a CIBAAuthRequest or a plain dict (some callers pass a
37
+ # raw dict, e.g. kobil_aitrust.trust.require_approval). Coercing here
38
+ # fixes every caller without breaking object-style callers.
39
+ if isinstance(request, dict):
40
+ request = CIBAAuthRequest(
41
+ login_hint=request["login_hint"],
42
+ binding_message=request.get("binding_message") or "Approval required",
43
+ scope=request.get("scope"),
44
+ requested_expiry=request.get("requested_expiry"),
45
+ tool_name=request.get("tool_name"),
46
+ )
47
+ if not self._cfg.client_secret:
48
+ raise OAuthError("invalid_client", "clientSecret is required for CIBA authentication")
49
+
50
+ body = {
51
+ "client_id": self._cfg.client_id,
52
+ "client_secret": self._cfg.client_secret,
53
+ "login_hint": request.login_hint,
54
+ "binding_message": request.binding_message,
55
+ }
56
+ if request.tool_name:
57
+ body["tool_name"] = request.tool_name
58
+ if request.scope:
59
+ body["scope"] = request.scope
60
+ elif self._cfg.scopes:
61
+ body["scope"] = " ".join(self._cfg.scopes)
62
+ if request.requested_expiry is not None:
63
+ body["requested_expiry"] = str(request.requested_expiry)
64
+ if self._cfg.audience:
65
+ body["audience"] = self._cfg.audience
66
+
67
+ headers = {
68
+ "Content-Type": "application/x-www-form-urlencoded",
69
+ "Accept": "application/json",
70
+ **(self._cfg.config.get("custom_headers") or {}),
71
+ }
72
+ timeout = self._cfg.timeout_ms / 1000
73
+ try:
74
+ async with httpx.AsyncClient(timeout=timeout) as http:
75
+ response = await http.post(self._cfg.backchannel_auth_endpoint, headers=headers, data=body)
76
+ except Exception as error: # noqa: BLE001
77
+ raise KobilSDKError(f"CIBA initiation request failed: {error}", "NETWORK_ERROR")
78
+
79
+ try:
80
+ data = response.json()
81
+ except Exception:
82
+ raise KobilSDKError(
83
+ f"CIBA initiation endpoint returned non-JSON response ({response.status_code}): {response.text[:200]}",
84
+ "INVALID_RESPONSE",
85
+ )
86
+ if not response.is_success:
87
+ raise OAuthError(data.get("error") or "unknown_error", data.get("error_description"), response.status_code)
88
+
89
+ return CIBAAuthResponse(
90
+ auth_req_id=data["auth_req_id"],
91
+ expires_in=data.get("expires_in", 120),
92
+ interval=data.get("interval", DEFAULT_POLL_INTERVAL_SECONDS),
93
+ status="pending",
94
+ )
95
+
96
+ async def poll_for_token(self, auth_req_id: str, interval: Optional[int] = None) -> AuthResult:
97
+ current_interval_ms = (interval if interval is not None else DEFAULT_POLL_INTERVAL_SECONDS) * 1000
98
+ deadline = _now_ms() + MAX_POLL_DURATION_SECONDS * 1000
99
+
100
+ while _now_ms() < deadline:
101
+ await asyncio.sleep(current_interval_ms / 1000)
102
+
103
+ body = {
104
+ "grant_type": "urn:openid:params:grant-type:ciba",
105
+ "client_id": self._cfg.client_id,
106
+ "auth_req_id": auth_req_id,
107
+ }
108
+ if self._cfg.client_secret:
109
+ body["client_secret"] = self._cfg.client_secret
110
+ headers = {
111
+ "Content-Type": "application/x-www-form-urlencoded",
112
+ "Accept": "application/json",
113
+ **(self._cfg.config.get("custom_headers") or {}),
114
+ }
115
+ timeout = self._cfg.timeout_ms / 1000
116
+ try:
117
+ async with httpx.AsyncClient(timeout=timeout) as http:
118
+ response = await http.post(self._cfg.token_endpoint, headers=headers, data=body)
119
+ except Exception as error: # noqa: BLE001
120
+ raise KobilSDKError(f"CIBA poll request failed: {error}", "NETWORK_ERROR")
121
+
122
+ try:
123
+ data = response.json()
124
+ except Exception:
125
+ raise KobilSDKError(
126
+ f"CIBA poll endpoint returned non-JSON response ({response.status_code}): {response.text[:200]}",
127
+ "INVALID_RESPONSE",
128
+ )
129
+
130
+ if response.is_success:
131
+ return AuthResult(
132
+ access_token=data["access_token"],
133
+ token_type=data.get("token_type", "Bearer"),
134
+ expires_in=data.get("expires_in", 3600),
135
+ refresh_token=data.get("refresh_token"),
136
+ id_token=data.get("id_token"),
137
+ dpop_nonce=response.headers.get("DPoP-Nonce"),
138
+ )
139
+
140
+ error_type = data.get("error")
141
+ if error_type == "slow_down":
142
+ current_interval_ms += 5000
143
+ continue
144
+ if error_type == "authorization_pending":
145
+ continue
146
+ if error_type == "access_denied":
147
+ raise AuthorizationDeniedError()
148
+ if error_type == "expired_token":
149
+ raise AuthorizationTimeoutError("CIBA authentication request expired")
150
+ raise OAuthError(error_type or "unknown_error", data.get("error_description"), response.status_code)
151
+
152
+ raise AuthorizationTimeoutError(
153
+ f"CIBA polling exceeded maximum duration of {MAX_POLL_DURATION_SECONDS}s"
154
+ )
155
+
156
+ async def get_status(self, auth_req_id: str) -> CIBAAuthResponse:
157
+ body = {
158
+ "grant_type": "urn:openid:params:grant-type:ciba",
159
+ "client_id": self._cfg.client_id,
160
+ "auth_req_id": auth_req_id,
161
+ }
162
+ if self._cfg.client_secret:
163
+ body["client_secret"] = self._cfg.client_secret
164
+ headers = {
165
+ "Content-Type": "application/x-www-form-urlencoded",
166
+ "Accept": "application/json",
167
+ **(self._cfg.config.get("custom_headers") or {}),
168
+ }
169
+ timeout = self._cfg.timeout_ms / 1000
170
+ try:
171
+ async with httpx.AsyncClient(timeout=timeout) as http:
172
+ response = await http.post(self._cfg.token_endpoint, headers=headers, data=body)
173
+ except Exception as error: # noqa: BLE001
174
+ raise KobilSDKError(f"CIBA status request failed: {error}", "NETWORK_ERROR")
175
+
176
+ try:
177
+ data = response.json()
178
+ except Exception:
179
+ raise KobilSDKError(
180
+ f"CIBA status endpoint returned non-JSON response ({response.status_code}): {response.text[:200]}",
181
+ "INVALID_RESPONSE",
182
+ )
183
+
184
+ if response.is_success:
185
+ return CIBAAuthResponse(
186
+ auth_req_id=auth_req_id,
187
+ expires_in=data.get("expires_in", 0),
188
+ interval=data.get("interval", DEFAULT_POLL_INTERVAL_SECONDS),
189
+ status="approved",
190
+ )
191
+
192
+ error_type = data.get("error")
193
+ if error_type in ("authorization_pending", "slow_down"):
194
+ return CIBAAuthResponse(
195
+ auth_req_id=auth_req_id,
196
+ expires_in=data.get("expires_in", 0),
197
+ interval=data.get("interval", DEFAULT_POLL_INTERVAL_SECONDS),
198
+ status="pending",
199
+ )
200
+ if error_type == "access_denied":
201
+ return CIBAAuthResponse(auth_req_id=auth_req_id, expires_in=0, interval=0, status="denied")
202
+ if error_type == "expired_token":
203
+ return CIBAAuthResponse(auth_req_id=auth_req_id, expires_in=0, interval=0, status="expired")
204
+ raise OAuthError(error_type or "unknown_error", data.get("error_description"), response.status_code)
205
+
206
+
207
+ __all__ = ["CIBAClient", "DEFAULT_POLL_INTERVAL_SECONDS", "MAX_POLL_DURATION_SECONDS"]