replicantx 0.1.0__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.
replicantx/__init__.py ADDED
@@ -0,0 +1,45 @@
1
+ # Copyright 2025 Helix Technologies Limited
2
+ # Licensed under the Apache License, Version 2.0 (see LICENSE file).
3
+ """
4
+ ReplicantX - End-to-end testing harness for AI agents via web service APIs.
5
+
6
+ This package provides tools for testing AI agents by calling their HTTP APIs
7
+ with configurable authentication, assertions, and reporting.
8
+ """
9
+
10
+ __version__ = "0.1.0"
11
+ __author__ = "ReplicantX Team"
12
+ __email__ = "team@replicantx.ai"
13
+
14
+ from .models import (
15
+ Message,
16
+ Step,
17
+ StepResult,
18
+ ScenarioConfig,
19
+ ScenarioReport,
20
+ AuthConfig,
21
+ AssertionResult,
22
+ TestSuiteReport,
23
+ AuthProvider,
24
+ TestLevel,
25
+ AssertionType,
26
+ ReplicantConfig,
27
+ LLMConfig,
28
+ )
29
+
30
+ __all__ = [
31
+ "__version__",
32
+ "Message",
33
+ "Step",
34
+ "StepResult",
35
+ "ScenarioConfig",
36
+ "ScenarioReport",
37
+ "AuthConfig",
38
+ "AssertionResult",
39
+ "TestSuiteReport",
40
+ "AuthProvider",
41
+ "TestLevel",
42
+ "AssertionType",
43
+ "ReplicantConfig",
44
+ "LLMConfig",
45
+ ]
@@ -0,0 +1,20 @@
1
+ # Copyright 2025 Helix Technologies Limited
2
+ # Licensed under the Apache License, Version 2.0 (see LICENSE file).
3
+ """
4
+ Authentication module for ReplicantX.
5
+
6
+ This module provides authentication providers for different services including
7
+ Supabase, JWT, and no-auth options.
8
+ """
9
+
10
+ from .base import AuthBase
11
+ from .supabase import SupabaseAuth
12
+ from .jwt import JWTAuth
13
+ from .noop import NoopAuth
14
+
15
+ __all__ = [
16
+ "AuthBase",
17
+ "SupabaseAuth",
18
+ "JWTAuth",
19
+ "NoopAuth",
20
+ ]
@@ -0,0 +1,74 @@
1
+ # Copyright 2025 Helix Technologies Limited
2
+ # Licensed under the Apache License, Version 2.0 (see LICENSE file).
3
+ """
4
+ Base authentication class for ReplicantX.
5
+
6
+ This module defines the abstract base class that all authentication providers
7
+ must inherit from to provide a consistent interface.
8
+ """
9
+
10
+ from abc import ABC, abstractmethod
11
+ from typing import Dict, Optional
12
+
13
+ from ..models import AuthConfig
14
+
15
+
16
+ class AuthBase(ABC):
17
+ """Abstract base class for authentication providers."""
18
+
19
+ def __init__(self, config: AuthConfig):
20
+ """Initialize the authentication provider.
21
+
22
+ Args:
23
+ config: Authentication configuration
24
+ """
25
+ self.config = config
26
+ self._token: Optional[str] = None
27
+ self._headers: Dict[str, str] = {}
28
+
29
+ @abstractmethod
30
+ async def authenticate(self) -> str:
31
+ """Authenticate and return a token.
32
+
33
+ Returns:
34
+ Authentication token
35
+
36
+ Raises:
37
+ AuthenticationError: If authentication fails
38
+ """
39
+ pass
40
+
41
+ @abstractmethod
42
+ async def get_headers(self) -> Dict[str, str]:
43
+ """Get authentication headers for HTTP requests.
44
+
45
+ Returns:
46
+ Dictionary of headers to include in requests
47
+ """
48
+ pass
49
+
50
+ async def token(self) -> str:
51
+ """Get the current authentication token.
52
+
53
+ This method caches the token and only re-authenticates if necessary.
54
+
55
+ Returns:
56
+ Current authentication token
57
+ """
58
+ if self._token is None:
59
+ self._token = await self.authenticate()
60
+ return self._token
61
+
62
+ def invalidate_token(self) -> None:
63
+ """Invalidate the current token, forcing re-authentication on next request."""
64
+ self._token = None
65
+ self._headers.clear()
66
+
67
+
68
+ class AuthenticationError(Exception):
69
+ """Raised when authentication fails."""
70
+
71
+ def __init__(self, message: str, provider: str):
72
+ self.message = message
73
+ self.provider = provider
74
+ super().__init__(f"Authentication failed for {provider}: {message}")
replicantx/auth/jwt.py ADDED
@@ -0,0 +1,102 @@
1
+ # Copyright 2025 Helix Technologies Limited
2
+ # Licensed under the Apache License, Version 2.0 (see LICENSE file).
3
+ """
4
+ JWT authentication provider for ReplicantX.
5
+
6
+ This module provides authentication using pre-minted JWT tokens,
7
+ typically provided via environment variables or configuration.
8
+ """
9
+
10
+ import os
11
+ from typing import Dict
12
+
13
+ from .base import AuthBase, AuthenticationError
14
+ from ..models import AuthConfig
15
+
16
+
17
+ class JWTAuth(AuthBase):
18
+ """JWT authentication provider using pre-minted tokens."""
19
+
20
+ def __init__(self, config: AuthConfig):
21
+ """Initialize JWT authentication.
22
+
23
+ Args:
24
+ config: Authentication configuration with JWT token
25
+ """
26
+ super().__init__(config)
27
+
28
+ def _substitute_env_vars(self, value: str) -> str:
29
+ """Substitute environment variables in string values.
30
+
31
+ Args:
32
+ value: String that may contain {{ env.VAR_NAME }} patterns
33
+
34
+ Returns:
35
+ String with environment variables substituted
36
+ """
37
+ if not value:
38
+ return value
39
+
40
+ # Simple template substitution for {{ env.VAR_NAME }}
41
+ import re
42
+ def replace_env_var(match):
43
+ var_name = match.group(1)
44
+ env_value = os.getenv(var_name)
45
+ if env_value is None:
46
+ raise ValueError(f"Environment variable {var_name} not found")
47
+ return env_value
48
+
49
+ return re.sub(r'\{\{\s*env\.([A-Z_]+)\s*\}\}', replace_env_var, value)
50
+
51
+ async def authenticate(self) -> str:
52
+ """Return the JWT token.
53
+
54
+ Returns:
55
+ JWT token for API requests
56
+
57
+ Raises:
58
+ AuthenticationError: If token is missing or invalid
59
+ """
60
+ try:
61
+ if not self.config.token:
62
+ raise AuthenticationError(
63
+ "JWT token not provided in configuration",
64
+ "jwt"
65
+ )
66
+
67
+ # Substitute environment variables in token
68
+ token = self._substitute_env_vars(self.config.token)
69
+
70
+ if not token:
71
+ raise AuthenticationError(
72
+ "JWT token is empty after environment variable substitution",
73
+ "jwt"
74
+ )
75
+
76
+ return token
77
+
78
+ except Exception as e:
79
+ if isinstance(e, AuthenticationError):
80
+ raise
81
+ raise AuthenticationError(
82
+ f"JWT authentication failed: {str(e)}",
83
+ "jwt"
84
+ )
85
+
86
+ async def get_headers(self) -> Dict[str, str]:
87
+ """Get authentication headers for HTTP requests.
88
+
89
+ Returns:
90
+ Dictionary with Authorization header
91
+ """
92
+ token = await self.token()
93
+ headers = {
94
+ "Authorization": f"Bearer {token}",
95
+ "Content-Type": "application/json",
96
+ }
97
+
98
+ # Add any additional headers from config
99
+ if self.config.headers:
100
+ headers.update(self.config.headers)
101
+
102
+ return headers
@@ -0,0 +1,49 @@
1
+ # Copyright 2025 Helix Technologies Limited
2
+ # Licensed under the Apache License, Version 2.0 (see LICENSE file).
3
+ """
4
+ No-op authentication provider for ReplicantX.
5
+
6
+ This module provides a no-authentication provider for testing purposes
7
+ or when working with APIs that don't require authentication.
8
+ """
9
+
10
+ from typing import Dict
11
+
12
+ from .base import AuthBase
13
+ from ..models import AuthConfig
14
+
15
+
16
+ class NoopAuth(AuthBase):
17
+ """No-op authentication provider that provides no authentication."""
18
+
19
+ def __init__(self, config: AuthConfig):
20
+ """Initialize noop authentication.
21
+
22
+ Args:
23
+ config: Authentication configuration (not used for noop)
24
+ """
25
+ super().__init__(config)
26
+
27
+ async def authenticate(self) -> str:
28
+ """Return empty token for no authentication.
29
+
30
+ Returns:
31
+ Empty string (no token needed)
32
+ """
33
+ return ""
34
+
35
+ async def get_headers(self) -> Dict[str, str]:
36
+ """Get authentication headers for HTTP requests.
37
+
38
+ Returns:
39
+ Dictionary with basic headers (no authentication)
40
+ """
41
+ headers = {
42
+ "Content-Type": "application/json",
43
+ }
44
+
45
+ # Add any additional headers from config
46
+ if self.config.headers:
47
+ headers.update(self.config.headers)
48
+
49
+ return headers
@@ -0,0 +1,147 @@
1
+ # Copyright 2025 Helix Technologies Limited
2
+ # Licensed under the Apache License, Version 2.0 (see LICENSE file).
3
+ """
4
+ Supabase authentication provider for ReplicantX.
5
+
6
+ This module provides authentication via Supabase's email/password flow,
7
+ managing session tokens and providing them as Bearer tokens for API requests.
8
+ """
9
+
10
+ import os
11
+ from typing import Dict
12
+
13
+ from supabase import create_client, Client
14
+
15
+ from .base import AuthBase, AuthenticationError
16
+ from ..models import AuthConfig
17
+
18
+
19
+ class SupabaseAuth(AuthBase):
20
+ """Supabase authentication provider using email/password."""
21
+
22
+ def __init__(self, config: AuthConfig):
23
+ """Initialize Supabase authentication.
24
+
25
+ Args:
26
+ config: Authentication configuration with Supabase credentials
27
+ """
28
+ super().__init__(config)
29
+ self._client: Client = None
30
+ self._session = None
31
+
32
+ def _get_client(self) -> Client:
33
+ """Get or create Supabase client.
34
+
35
+ Returns:
36
+ Supabase client instance
37
+
38
+ Raises:
39
+ AuthenticationError: If client creation fails
40
+ """
41
+ if self._client is None:
42
+ try:
43
+ # Template substitution for environment variables
44
+ project_url = self._substitute_env_vars(self.config.project_url)
45
+ api_key = self._substitute_env_vars(self.config.api_key)
46
+
47
+ self._client = create_client(project_url, api_key)
48
+ except Exception as e:
49
+ raise AuthenticationError(
50
+ f"Failed to create Supabase client: {str(e)}",
51
+ "supabase"
52
+ )
53
+
54
+ return self._client
55
+
56
+ def _substitute_env_vars(self, value: str) -> str:
57
+ """Substitute environment variables in string values.
58
+
59
+ Args:
60
+ value: String that may contain {{ env.VAR_NAME }} patterns
61
+
62
+ Returns:
63
+ String with environment variables substituted
64
+ """
65
+ if not value:
66
+ return value
67
+
68
+ # Simple template substitution for {{ env.VAR_NAME }}
69
+ import re
70
+ def replace_env_var(match):
71
+ var_name = match.group(1)
72
+ env_value = os.getenv(var_name)
73
+ if env_value is None:
74
+ raise ValueError(f"Environment variable {var_name} not found")
75
+ return env_value
76
+
77
+ return re.sub(r'\{\{\s*env\.([A-Z_]+)\s*\}\}', replace_env_var, value)
78
+
79
+ async def authenticate(self) -> str:
80
+ """Authenticate with Supabase using email/password.
81
+
82
+ Returns:
83
+ Access token for API requests
84
+
85
+ Raises:
86
+ AuthenticationError: If authentication fails
87
+ """
88
+ try:
89
+ client = self._get_client()
90
+
91
+ # Substitute environment variables in credentials
92
+ email = self._substitute_env_vars(self.config.email)
93
+ password = self._substitute_env_vars(self.config.password)
94
+
95
+ # Sign in with email/password
96
+ auth_response = client.auth.sign_in_with_password({
97
+ "email": email,
98
+ "password": password
99
+ })
100
+
101
+ if not auth_response.session:
102
+ raise AuthenticationError(
103
+ "No session returned from Supabase authentication",
104
+ "supabase"
105
+ )
106
+
107
+ self._session = auth_response.session
108
+ return auth_response.session.access_token
109
+
110
+ except Exception as e:
111
+ if isinstance(e, AuthenticationError):
112
+ raise
113
+ raise AuthenticationError(
114
+ f"Supabase authentication failed: {str(e)}",
115
+ "supabase"
116
+ )
117
+
118
+ async def get_headers(self) -> Dict[str, str]:
119
+ """Get authentication headers for HTTP requests.
120
+
121
+ Returns:
122
+ Dictionary with Authorization header
123
+ """
124
+ token = await self.token()
125
+ headers = {
126
+ "Authorization": f"Bearer {token}",
127
+ "Content-Type": "application/json",
128
+ }
129
+
130
+ # Add any additional headers from config
131
+ if self.config.headers:
132
+ headers.update(self.config.headers)
133
+
134
+ return headers
135
+
136
+ def invalidate_token(self) -> None:
137
+ """Invalidate current session and token."""
138
+ super().invalidate_token()
139
+ self._session = None
140
+
141
+ # Sign out from Supabase if we have a client
142
+ if self._client:
143
+ try:
144
+ self._client.auth.sign_out()
145
+ except Exception:
146
+ # Ignore errors during sign out
147
+ pass