agentsts-core 0.0.2__py3-none-any.whl

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,11 @@
1
+ from ._actor_service import ActorTokenService
2
+ from ._base import STSIntegrationBase
3
+ from .client import STSClient, STSConfig, TokenType
4
+
5
+ __all__ = [
6
+ "STSIntegrationBase",
7
+ "ActorTokenService",
8
+ "STSClient",
9
+ "STSConfig",
10
+ "TokenType",
11
+ ]
@@ -0,0 +1,51 @@
1
+ """Base actor token service for STS integration."""
2
+
3
+ import logging
4
+ from typing import Optional
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ SERVICE_ACCOUNT_TOKEN_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/token"
9
+
10
+
11
+ class ActorTokenService:
12
+ """Service that loads actor tokens for STS delegation.
13
+
14
+ This service provides a simple, synchronous approach for loading actor tokens
15
+ (like Kubernetes service account tokens) used in STS token exchange.
16
+ """
17
+
18
+ def __init__(self, token_path: Optional[str] = None):
19
+ """Initialize the actor token service.
20
+
21
+ Args:
22
+ token_path: Path to the token file. Defaults to Kubernetes service account token path.
23
+ """
24
+ self.token_path = token_path or SERVICE_ACCOUNT_TOKEN_PATH
25
+
26
+ def get_actor_token(self) -> Optional[str]:
27
+ """Get the actor token for STS delegation.
28
+
29
+ This method reads the token from the file each time it's called.
30
+ If loading fails, it returns None.
31
+
32
+ Returns:
33
+ Actor token string if available, None otherwise
34
+ """
35
+ try:
36
+ logger.debug(f"Loading actor token from {self.token_path}")
37
+
38
+ with open(self.token_path, "r", encoding="utf-8") as f:
39
+ token = f.read().strip()
40
+
41
+ if token:
42
+ logger.info("Successfully loaded actor token'")
43
+ return token
44
+ else:
45
+ logger.warning(f"No actor token found at {self.token_path}")
46
+ return None
47
+
48
+ except Exception as e:
49
+ logger.error(f"Failed to load actor token': {e}")
50
+ logger.error(f"Token path: {self.token_path}")
51
+ return None
agentsts/core/_base.py ADDED
@@ -0,0 +1,99 @@
1
+ """Base classes for framework-specific STS integration."""
2
+
3
+ import logging
4
+ from abc import ABC, abstractmethod
5
+ from typing import Any, Dict, Optional, Union
6
+
7
+ from ._actor_service import ActorTokenService
8
+ from .client import STSClient, STSConfig, TokenType
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class STSIntegrationBase(ABC):
14
+ """Base class for framework-specific STS integrations."""
15
+
16
+ def __init__(
17
+ self,
18
+ well_known_uri: str,
19
+ service_account_token_path: Optional[str] = None,
20
+ timeout: int = 30,
21
+ verify_ssl: bool = True,
22
+ additional_config: Optional[Dict[str, Any]] = None,
23
+ ):
24
+ """Initialize the STS integration.
25
+
26
+ Args:
27
+ well_known_uri: Well-known configuration URI for the STS server
28
+ timeout: Request timeout in seconds
29
+ verify_ssl: Whether to verify SSL certificates
30
+ additional_config: Additional configuration for the specific framework
31
+ """
32
+ self.well_known_uri = well_known_uri
33
+ self.timeout = timeout
34
+ self.verify_ssl = verify_ssl
35
+ self.additional_config = additional_config or {}
36
+
37
+ # Initialize STS client
38
+ config = STSConfig(
39
+ well_known_uri=well_known_uri,
40
+ timeout=timeout,
41
+ verify_ssl=verify_ssl,
42
+ )
43
+ self.sts_client = STSClient(config)
44
+ self.access_token = None # cached access token
45
+ self._actor_token = ActorTokenService(service_account_token_path).get_actor_token()
46
+
47
+ @abstractmethod
48
+ def create_auth_credential(self, access_token: str) -> Any:
49
+ """create a framework specific auth credential object from an access token."""
50
+ pass
51
+
52
+ async def exchange_token(
53
+ self,
54
+ subject_token: str,
55
+ subject_token_type: TokenType = TokenType.JWT,
56
+ actor_token: Optional[str] = None,
57
+ actor_token_type: Optional[TokenType] = None,
58
+ resource: Optional[Union[str, list]] = None,
59
+ audience: Optional[Union[str, list]] = None,
60
+ scope: Optional[str] = None,
61
+ requested_token_type: Optional[TokenType] = None,
62
+ additional_parameters: Optional[Dict[str, Any]] = None,
63
+ ) -> str:
64
+ """Exchange token using STS.
65
+
66
+ Args:
67
+ subject_token: The security token representing the identity
68
+ subject_token_type: Type of the subject token
69
+ actor_token: The security token representing the identity of the acting party
70
+ actor_token_type: Type of the actor token
71
+ resource: The logical name of the target service or resource
72
+ audience: The logical name of the target service or resource
73
+ scope: The scope of the requested token
74
+ requested_token_type: The type of the requested token
75
+ additional_parameters: Additional parameters for the request
76
+
77
+ Returns:
78
+ Access token
79
+
80
+ Raises:
81
+ TokenExchangeError: If token exchange fails
82
+ """
83
+ try:
84
+ response = await self.sts_client.exchange_token(
85
+ subject_token=subject_token,
86
+ subject_token_type=subject_token_type,
87
+ actor_token=actor_token,
88
+ actor_token_type=actor_token_type,
89
+ resource=resource,
90
+ audience=audience,
91
+ scope=scope,
92
+ requested_token_type=requested_token_type,
93
+ additional_parameters=additional_parameters,
94
+ )
95
+ logger.debug(f"Successfully obtained access token for ADK with length: {len(response.access_token)}")
96
+ return response.access_token
97
+ except Exception as e:
98
+ logger.error(f"Token exchange failed: {e}")
99
+ raise
@@ -0,0 +1,23 @@
1
+ from ._client import STSClient
2
+ from ._config import STSConfig
3
+ from ._exceptions import AuthenticationError, ConfigurationError, NetworkError, STSError, TokenExchangeError
4
+ from ._models import GrantType, TokenExchangeRequest, TokenExchangeResponse, TokenType, WellKnownConfiguration
5
+ from ._models import TokenExchangeError as TokenExchangeErrorModel
6
+
7
+ __version__ = "0.1.0"
8
+
9
+ __all__ = [
10
+ "STSClient",
11
+ "STSConfig",
12
+ "STSError",
13
+ "TokenExchangeError",
14
+ "ConfigurationError",
15
+ "AuthenticationError",
16
+ "NetworkError",
17
+ "TokenExchangeRequest",
18
+ "TokenExchangeResponse",
19
+ "TokenExchangeErrorModel",
20
+ "TokenType",
21
+ "GrantType",
22
+ "WellKnownConfiguration",
23
+ ]
@@ -0,0 +1,217 @@
1
+ import logging
2
+ from typing import Any, Dict, Optional, Union
3
+
4
+ import httpx
5
+
6
+ from ._config import STSConfig
7
+ from ._exceptions import AuthenticationError, NetworkError, TokenExchangeError
8
+ from ._models import TokenExchangeRequest, TokenExchangeResponse, TokenType, WellKnownConfiguration
9
+ from ._utils import fetch_well_known_configuration, parse_token_exchange_error
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class STSClient:
15
+ """Security Token Service client implementing RFC 8693 OAuth 2.0 Token Exchange."""
16
+
17
+ def __init__(
18
+ self,
19
+ config: STSConfig,
20
+ ):
21
+ """
22
+ Initialize STS client.
23
+
24
+ Args:
25
+ config: STS configuration
26
+ """
27
+ self.config = config
28
+ self._well_known_config: Optional[WellKnownConfiguration] = None
29
+ self._http_client: Optional[httpx.AsyncClient] = None
30
+
31
+ async def __aenter__(self):
32
+ """Async context manager entry."""
33
+ await self._initialize()
34
+ return self
35
+
36
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
37
+ """Async context manager exit."""
38
+ await self.close()
39
+
40
+ async def _initialize(self):
41
+ """Initialize the client by fetching well-known configuration."""
42
+ if not self._well_known_config:
43
+ self._well_known_config = await fetch_well_known_configuration(
44
+ self.config.well_known_uri, self.config.timeout, self.config.verify_ssl
45
+ )
46
+
47
+ if not self._http_client:
48
+ self._http_client = httpx.AsyncClient(timeout=self.config.timeout, verify=self.config.verify_ssl)
49
+
50
+ async def close(self):
51
+ """Close the HTTP client."""
52
+ if self._http_client:
53
+ await self._http_client.aclose()
54
+ self._http_client = None
55
+
56
+ def _build_request_data(self, request: TokenExchangeRequest) -> Dict[str, Any]:
57
+ """Build form data for the token exchange request."""
58
+ data = {
59
+ "grant_type": request.grant_type.value,
60
+ "subject_token": request.subject_token,
61
+ "subject_token_type": request.subject_token_type.value,
62
+ }
63
+
64
+ # Add actor token for delegation requests
65
+ if request.actor_token:
66
+ data["actor_token"] = request.actor_token
67
+ data["actor_token_type"] = request.actor_token_type.value
68
+
69
+ # Add optional parameters
70
+ if request.resource:
71
+ data["resource"] = request.resource
72
+ if request.audience:
73
+ data["audience"] = request.audience
74
+ if request.scope:
75
+ data["scope"] = request.scope
76
+ if request.requested_token_type:
77
+ data["requested_token_type"] = request.requested_token_type.value
78
+
79
+ # Add additional parameters
80
+ if request.additional_parameters:
81
+ data.update(request.additional_parameters)
82
+
83
+ return data
84
+
85
+ async def exchange_token(
86
+ self,
87
+ subject_token: str,
88
+ subject_token_type: TokenType = TokenType.JWT,
89
+ actor_token: Optional[str] = None,
90
+ actor_token_type: Optional[TokenType] = None,
91
+ resource: Optional[Union[str, list]] = None,
92
+ audience: Optional[Union[str, list]] = None,
93
+ scope: Optional[str] = None,
94
+ requested_token_type: Optional[TokenType] = None,
95
+ additional_parameters: Optional[Dict[str, Any]] = None,
96
+ ) -> TokenExchangeResponse:
97
+ """
98
+ Exchange a token using RFC 8693 OAuth 2.0 Token Exchange.
99
+
100
+ Args:
101
+ subject_token: The security token representing the identity
102
+ subject_token_type: Type of the subject token
103
+ actor_token: The security token representing the identity of the acting party
104
+ actor_token_type: Type of the actor token
105
+ resource: The logical name of the target service or resource
106
+ audience: The logical name of the target service or resource
107
+ scope: The scope of the requested token
108
+ requested_token_type: The type of the requested token
109
+ additional_parameters: Additional parameters for the request
110
+
111
+ Returns:
112
+ TokenExchangeResponse containing the issued token
113
+
114
+ Raises:
115
+ TokenExchangeError: If token exchange fails
116
+ NetworkError: If network operation fails
117
+ """
118
+ await self._initialize()
119
+
120
+ # Build the request
121
+ request = TokenExchangeRequest(
122
+ subject_token=subject_token,
123
+ subject_token_type=subject_token_type,
124
+ actor_token=actor_token,
125
+ actor_token_type=actor_token_type,
126
+ resource=resource,
127
+ audience=audience,
128
+ scope=scope,
129
+ requested_token_type=requested_token_type,
130
+ additional_parameters=additional_parameters,
131
+ )
132
+
133
+ # Prepare the request
134
+ data = self._build_request_data(request)
135
+
136
+ try:
137
+ response = await self._http_client.post(self._well_known_config.token_endpoint, data=data)
138
+
139
+ if response.status_code == 200:
140
+ response_data = response.json()
141
+ result = TokenExchangeResponse.model_validate(response_data)
142
+ return result
143
+ else:
144
+ # Parse error response
145
+ try:
146
+ response_data = response.json()
147
+ error = parse_token_exchange_error(response_data)
148
+ raise TokenExchangeError(
149
+ error=error.error, error_description=error.error_description, status_code=response.status_code
150
+ )
151
+ except (ValueError, KeyError, TypeError) as e:
152
+ response_text = response.text
153
+ raise TokenExchangeError(
154
+ error="invalid_response",
155
+ error_description=f"Invalid error response: {response_text}",
156
+ status_code=response.status_code,
157
+ ) from e
158
+
159
+ except httpx.RequestError as e:
160
+ raise NetworkError(f"Network error during token exchange: {e}") from e
161
+
162
+ async def impersonate(
163
+ self, subject_token: str, subject_token_type: TokenType = TokenType.JWT, **kwargs
164
+ ) -> TokenExchangeResponse:
165
+ """
166
+ Perform impersonation token exchange (no actor token).
167
+
168
+ Args:
169
+ subject_token: The security token representing the identity to impersonate
170
+ subject_token_type: Type of the subject token
171
+ **kwargs: Additional parameters for the token exchange
172
+
173
+ Returns:
174
+ TokenExchangeResponse containing the issued token
175
+ """
176
+ try:
177
+ result = await self.exchange_token(
178
+ subject_token=subject_token, subject_token_type=subject_token_type, **kwargs
179
+ )
180
+ return result
181
+ except Exception as e:
182
+ logger.error(f"Exception in impersonate method: {type(e)} - {e}")
183
+ logger.error(f"Exception args: {e.args}")
184
+ raise
185
+
186
+ async def delegate(
187
+ self, subject_token: str, subject_token_type: TokenType, actor_token: str, actor_token_type: TokenType, **kwargs
188
+ ) -> TokenExchangeResponse:
189
+ """
190
+ Perform delegation token exchange (with actor token).
191
+
192
+ Args:
193
+ subject_token: The security token representing the identity to delegate
194
+ subject_token_type: Type of the subject token
195
+ actor_token: The security token representing the identity of the acting party
196
+ actor_token_type: Type of the actor token
197
+ **kwargs: Additional parameters for the token exchange
198
+
199
+ Returns:
200
+ TokenExchangeResponse containing the issued token
201
+ """
202
+ if not subject_token:
203
+ raise AuthenticationError("Subject token required for delegation")
204
+
205
+ try:
206
+ result = await self.exchange_token(
207
+ subject_token=subject_token,
208
+ subject_token_type=subject_token_type,
209
+ actor_token=actor_token,
210
+ actor_token_type=actor_token_type,
211
+ **kwargs,
212
+ )
213
+ return result
214
+ except Exception as e:
215
+ logger.error(f"Exception in delegate method: {type(e)} - {e}")
216
+ logger.error(f"Exception args: {e.args}")
217
+ raise
@@ -0,0 +1,9 @@
1
+ from pydantic import BaseModel, Field
2
+
3
+
4
+ class STSConfig(BaseModel):
5
+ """Configuration for STS client."""
6
+
7
+ well_known_uri: str = Field(..., description="The well-known configuration URI")
8
+ timeout: int = Field(default=5, description="Request timeout in seconds")
9
+ verify_ssl: bool = Field(default=True, description="Whether to verify SSL certificates")
@@ -0,0 +1,35 @@
1
+ from typing import Optional
2
+
3
+
4
+ class STSError(Exception):
5
+ """Base exception for STS client errors."""
6
+
7
+ pass
8
+
9
+
10
+ class TokenExchangeError(STSError):
11
+ """Exception raised when token exchange fails."""
12
+
13
+ def __init__(self, error: str, error_description: Optional[str] = None, status_code: Optional[int] = None):
14
+ self.error = error
15
+ self.error_description = error_description
16
+ self.status_code = status_code
17
+ super().__init__(f"Token exchange failed: {error} - {error_description}")
18
+
19
+
20
+ class ConfigurationError(STSError):
21
+ """Exception raised when STS configuration is invalid."""
22
+
23
+ pass
24
+
25
+
26
+ class AuthenticationError(STSError):
27
+ """Exception raised when authentication fails."""
28
+
29
+ pass
30
+
31
+
32
+ class NetworkError(STSError):
33
+ """Exception raised when network operations fail."""
34
+
35
+ pass
@@ -0,0 +1,91 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import Enum
4
+ from typing import Any, Dict, List, Optional, Union
5
+
6
+ from pydantic import BaseModel, Field, field_validator, model_validator
7
+
8
+
9
+ class TokenType(str, Enum):
10
+ """RFC 8693 defined token types."""
11
+
12
+ JWT = "urn:ietf:params:oauth:token-type:jwt"
13
+ SAML2 = "urn:ietf:params:oauth:token-type:saml2"
14
+ SAML1 = "urn:ietf:params:oauth:token-type:saml1"
15
+ ID_TOKEN = "urn:ietf:params:oauth:token-type:id_token"
16
+ ACCESS_TOKEN = "urn:ietf:params:oauth:token-type:access_token"
17
+
18
+
19
+ class GrantType(str, Enum):
20
+ """OAuth 2.0 grant types."""
21
+
22
+ TOKEN_EXCHANGE = "urn:ietf:params:oauth:grant-type:token-exchange"
23
+
24
+
25
+ class TokenExchangeRequest(BaseModel):
26
+ """RFC 8693 Token Exchange Request model."""
27
+
28
+ grant_type: GrantType = GrantType.TOKEN_EXCHANGE
29
+ subject_token: str = Field(
30
+ ...,
31
+ description="The security token representing the identity of the party on behalf of whom the new token is being requested",
32
+ )
33
+ subject_token_type: TokenType = Field(..., description="The type of the subject_token")
34
+ actor_token: Optional[str] = Field(
35
+ None, description="The security token representing the identity of the acting party"
36
+ )
37
+ actor_token_type: Optional[TokenType] = Field(None, description="The type of the actor_token")
38
+ resource: Optional[Union[str, List[str]]] = Field(
39
+ None, description="The logical name of the target service or resource"
40
+ )
41
+ audience: Optional[Union[str, List[str]]] = Field(
42
+ None, description="The logical name of the target service or resource"
43
+ )
44
+ scope: Optional[str] = Field(None, description="The scope of the requested token")
45
+ requested_token_type: Optional[TokenType] = Field(None, description="The type of the requested token")
46
+ additional_parameters: Optional[Dict[str, Any]] = Field(None, description="Additional parameters for the request")
47
+
48
+ @model_validator(mode="after")
49
+ def actor_token_type_required_with_actor_token(self):
50
+ if self.actor_token and not self.actor_token_type:
51
+ raise ValueError("actor_token_type is required when actor_token is provided")
52
+ return self
53
+
54
+ def is_delegation_request(self) -> bool:
55
+ """Check if this is a delegation request (has actor_token)."""
56
+ return self.actor_token is not None
57
+
58
+ def is_impersonation_request(self) -> bool:
59
+ """Check if this is an impersonation request (no actor_token)."""
60
+ return self.actor_token is None
61
+
62
+
63
+ class TokenExchangeResponse(BaseModel):
64
+ """RFC 8693 Token Exchange Response model."""
65
+
66
+ access_token: str = Field(..., description="The issued security token")
67
+ issued_token_type: TokenType = Field(..., description="The type of the issued token")
68
+ token_type: str = Field(default="Bearer", description="The type of the access token")
69
+ expires_in: Optional[int] = Field(None, description="The lifetime in seconds of the access token")
70
+ scope: Optional[str] = Field(None, description="The scope of the access token")
71
+ refresh_token: Optional[str] = Field(None, description="Refresh token if applicable")
72
+ additional_parameters: Optional[Dict[str, Any]] = Field(None, description="Additional response parameters")
73
+
74
+
75
+ class TokenExchangeError(BaseModel):
76
+ """RFC 8693 Token Exchange Error model."""
77
+
78
+ error: str = Field(..., description="Error code")
79
+ error_description: Optional[str] = Field(None, description="Human-readable error description")
80
+ error_uri: Optional[str] = Field(None, description="URI identifying the error")
81
+ additional_parameters: Optional[Dict[str, Any]] = Field(None, description="Additional error parameters")
82
+
83
+
84
+ class WellKnownConfiguration(BaseModel):
85
+ """OAuth 2.0 Authorization Server Metadata model."""
86
+
87
+ issuer: str = Field(..., description="The authorization server's issuer identifier")
88
+ token_endpoint: str = Field(..., description="The token endpoint URL")
89
+ token_endpoint_auth_methods_supported: List[str] = Field(default_factory=list)
90
+ token_endpoint_auth_signing_alg_values_supported: List[str] = Field(default_factory=list)
91
+ additional_parameters: Optional[Dict[str, Any]] = Field(None, description="Additional configuration parameters")
@@ -0,0 +1,62 @@
1
+ import json
2
+ from typing import Any, Dict
3
+
4
+ import httpx
5
+ from pydantic import ValidationError
6
+
7
+ from ._exceptions import ConfigurationError, NetworkError
8
+ from ._exceptions import TokenExchangeError as TokenExchangeException
9
+ from ._models import WellKnownConfiguration
10
+
11
+ # Protocol constants
12
+ HTTP_PROTOCOL = "http://"
13
+ HTTPS_PROTOCOL = "https://"
14
+
15
+
16
+ async def fetch_well_known_configuration(
17
+ well_known_uri: str, timeout: int = 5, verify_ssl: bool = True
18
+ ) -> WellKnownConfiguration:
19
+ try:
20
+ async with httpx.AsyncClient(timeout=timeout, verify=verify_ssl) as client:
21
+ response = await client.get(well_known_uri)
22
+ response.raise_for_status()
23
+
24
+ data = response.json()
25
+
26
+ # add protocol to token_endpoint if it's missing
27
+ if "token_endpoint" in data and not data["token_endpoint"].startswith((HTTP_PROTOCOL, HTTPS_PROTOCOL)):
28
+ # use the protocol from the well_known_uri
29
+ if well_known_uri.startswith(HTTPS_PROTOCOL):
30
+ protocol = HTTPS_PROTOCOL
31
+ else:
32
+ protocol = HTTP_PROTOCOL
33
+ data["token_endpoint"] = protocol + data["token_endpoint"]
34
+
35
+ config = WellKnownConfiguration.model_validate(data)
36
+ return config
37
+
38
+ except httpx.HTTPStatusError as e:
39
+ raise NetworkError(f"Failed to fetch well-known configuration: HTTP {e.response.status_code}") from e
40
+ except httpx.RequestError as e:
41
+ raise NetworkError(f"Network error fetching well-known configuration: {e}") from e
42
+ except (json.JSONDecodeError, ValidationError) as e:
43
+ raise ConfigurationError(f"Invalid well-known configuration response: {e}") from e
44
+
45
+
46
+ def parse_token_exchange_error(response_data: Dict[str, Any]) -> TokenExchangeException:
47
+ """Parse token exchange error response."""
48
+ return TokenExchangeException(
49
+ error=response_data.get("error", "unknown_error"),
50
+ error_description=response_data.get("error_description"),
51
+ )
52
+
53
+
54
+ def extract_jwt_claims(token: str) -> Dict[str, Any]:
55
+ """Extract claims from a JWT token without verification."""
56
+ try:
57
+ import jwt
58
+
59
+ # Decode without verification to extract claims
60
+ return jwt.decode(token, options={"verify_signature": False})
61
+ except Exception as e:
62
+ raise ValueError(f"Failed to extract JWT claims: {e}") from e
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentsts-core
3
+ Version: 0.0.2
4
+ Summary: Security Token Service client implementing RFC 8693 OAuth 2.0 Token Exchange
5
+ Requires-Python: >=3.11.0
6
+ Requires-Dist: cryptography>=41.0.0
7
+ Requires-Dist: httpx>=0.25.0
8
+ Requires-Dist: pydantic>=2.5.0
9
+ Requires-Dist: pyjwt>=2.8.0
10
+ Requires-Dist: typing-extensions>=4.8.0
11
+ Provides-Extra: test
12
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == 'test'
13
+ Requires-Dist: pytest-mock>=3.0.0; extra == 'test'
14
+ Requires-Dist: pytest>=7.0.0; extra == 'test'
@@ -0,0 +1,12 @@
1
+ agentsts/core/__init__.py,sha256=Jcy4HnEVedco0WfFdzc6-1DSkMCIKlf3RWtUDeT7Q_Q,253
2
+ agentsts/core/_actor_service.py,sha256=SAgk-E0KwNs0_MxH37d_fWJ99PmYeIcXSTtssKe3lmU,1685
3
+ agentsts/core/_base.py,sha256=qKzKEWoD4TVN8wv4HcO_dRJ2dAho0ndOZ_MsOpSChVA,3751
4
+ agentsts/core/client/__init__.py,sha256=IgYBFJp8aJk-JbYuFgkdIJUsxSX1YLKt3jfhxs_HUwY,688
5
+ agentsts/core/client/_client.py,sha256=Ryko-Y_Rourlrhgc3ygt56gLfc4-sfqN0hP_8g7LZ0I,8297
6
+ agentsts/core/client/_config.py,sha256=x3NrAozxKy2Jel93vNcFDHzyaRFMCyOsHoL4XPwxKE8,365
7
+ agentsts/core/client/_exceptions.py,sha256=giYHNE3m_TI_Am7qY0l9iwIRq7X5MF0x2ZfV0tB4rzM,831
8
+ agentsts/core/client/_models.py,sha256=n_cFmEay-3HTaWbV7xj3JsgRszEEmvbsZwFOtO2ec7A,4237
9
+ agentsts/core/client/_utils.py,sha256=X5b5hQ1PVNCE7sh-bH_7ykX1WkeF_R3HixwXsv5yD94,2386
10
+ agentsts_core-0.0.2.dist-info/METADATA,sha256=j7QAvqExLcJiZENhHqTKq8PGS3V_V1tiFTLm-FdkYeI,506
11
+ agentsts_core-0.0.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
12
+ agentsts_core-0.0.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any