sinas 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.
sinas/__init__.py ADDED
@@ -0,0 +1,20 @@
1
+ """SINAS Python SDK - AI Agent Platform Client."""
2
+
3
+ from sinas.client import SinasClient
4
+ from sinas.exceptions import (
5
+ SinasError,
6
+ SinasAPIError,
7
+ SinasAuthError,
8
+ SinasNotFoundError,
9
+ SinasValidationError,
10
+ )
11
+
12
+ __version__ = "0.1.0"
13
+ __all__ = [
14
+ "SinasClient",
15
+ "SinasError",
16
+ "SinasAPIError",
17
+ "SinasAuthError",
18
+ "SinasNotFoundError",
19
+ "SinasValidationError",
20
+ ]
sinas/auth.py ADDED
@@ -0,0 +1,100 @@
1
+ """Runtime Authentication API."""
2
+
3
+ from typing import TYPE_CHECKING, Any, Dict
4
+
5
+ if TYPE_CHECKING:
6
+ from sinas.client import SinasClient
7
+
8
+
9
+ class AuthAPI:
10
+ """Runtime Authentication API methods."""
11
+
12
+ def __init__(self, client: "SinasClient") -> None:
13
+ self._client = client
14
+
15
+ def login(self, email: str) -> Dict[str, Any]:
16
+ """Initiate login by sending OTP to email.
17
+
18
+ Args:
19
+ email: User's email address.
20
+
21
+ Returns:
22
+ Login response with message and session_id.
23
+ Example: {"message": "OTP sent to your email", "session_id": "uuid-here"}
24
+ """
25
+ return self._client._request("POST", "/auth/login", json={"email": email})
26
+
27
+ def verify_otp(self, session_id: str, otp_code: str) -> Dict[str, Any]:
28
+ """Verify OTP and get access + refresh tokens.
29
+
30
+ Args:
31
+ session_id: Session ID from login() response.
32
+ otp_code: One-time password code from email.
33
+
34
+ Returns:
35
+ Response with access token, refresh token, and user info.
36
+ Example: {"access_token": "jwt-token", "refresh_token": "refresh-token",
37
+ "token_type": "bearer", "expires_in": 900, "user": {...}}
38
+ """
39
+ response = self._client._request(
40
+ "POST", "/auth/verify-otp",
41
+ json={"session_id": session_id, "otp_code": otp_code}
42
+ )
43
+ # Automatically set the token on the client
44
+ if "access_token" in response:
45
+ self._client.set_token(response["access_token"])
46
+ return response
47
+
48
+ def external_auth(self, token: str) -> Dict[str, Any]:
49
+ """Exchange external OIDC token for SINAS JWT.
50
+
51
+ Args:
52
+ token: External OIDC token.
53
+
54
+ Returns:
55
+ Response with access token, refresh token, and user info.
56
+ """
57
+ response = self._client._request(
58
+ "POST", "/auth/external-auth",
59
+ json={"token": token}
60
+ )
61
+ if "access_token" in response:
62
+ self._client.set_token(response["access_token"])
63
+ return response
64
+
65
+ def refresh(self, refresh_token: str) -> Dict[str, Any]:
66
+ """Refresh access token using refresh token.
67
+
68
+ Args:
69
+ refresh_token: Refresh token from login/verify response.
70
+
71
+ Returns:
72
+ Response with new access token.
73
+ Example: {"access_token": "jwt-token", "token_type": "bearer", "expires_in": 900}
74
+ """
75
+ response = self._client._request(
76
+ "POST", "/auth/refresh",
77
+ json={"refresh_token": refresh_token}
78
+ )
79
+ if "access_token" in response:
80
+ self._client.set_token(response["access_token"])
81
+ return response
82
+
83
+ def logout(self, refresh_token: str) -> None:
84
+ """Logout by revoking refresh token.
85
+
86
+ Args:
87
+ refresh_token: Refresh token to revoke.
88
+ """
89
+ self._client._request(
90
+ "POST", "/auth/logout",
91
+ json={"refresh_token": refresh_token}
92
+ )
93
+
94
+ def get_me(self) -> Dict[str, Any]:
95
+ """Get current authenticated user info.
96
+
97
+ Returns:
98
+ User information.
99
+ """
100
+ return self._client._request("GET", "/auth/me")
sinas/chats.py ADDED
@@ -0,0 +1,130 @@
1
+ """Runtime Chats API."""
2
+
3
+ from typing import TYPE_CHECKING, Any, Dict, Iterator, List, Optional, Union
4
+
5
+ if TYPE_CHECKING:
6
+ from sinas.client import SinasClient
7
+
8
+
9
+ class ChatsAPI:
10
+ """Runtime Chats API methods."""
11
+
12
+ def __init__(self, client: "SinasClient") -> None:
13
+ self._client = client
14
+
15
+ def create(
16
+ self,
17
+ namespace: str,
18
+ agent_name: str,
19
+ input: Optional[Dict[str, Any]] = None,
20
+ title: Optional[str] = None,
21
+ ) -> Dict[str, Any]:
22
+ """Create a new chat with an agent.
23
+
24
+ Args:
25
+ namespace: Agent namespace.
26
+ agent_name: Agent name.
27
+ input: Optional input data validated against agent's input_schema.
28
+ title: Optional chat title.
29
+
30
+ Returns:
31
+ Created chat information.
32
+ """
33
+ data: Dict[str, Any] = {}
34
+ if input is not None:
35
+ data["input"] = input
36
+ if title is not None:
37
+ data["title"] = title
38
+
39
+ return self._client._request(
40
+ "POST",
41
+ f"/agents/{namespace}/{agent_name}/chats",
42
+ json=data
43
+ )
44
+
45
+ def send(
46
+ self,
47
+ chat_id: str,
48
+ content: Union[str, List[Dict[str, Any]]],
49
+ ) -> Dict[str, Any]:
50
+ """Send message to chat and get response (blocking).
51
+
52
+ Args:
53
+ chat_id: Chat ID (UUID).
54
+ content: Message content (string or list of content blocks).
55
+
56
+ Returns:
57
+ Message response from the agent.
58
+ """
59
+ return self._client._request(
60
+ "POST",
61
+ f"/chats/{chat_id}/messages",
62
+ json={"content": content}
63
+ )
64
+
65
+ def stream(
66
+ self,
67
+ chat_id: str,
68
+ content: Union[str, List[Dict[str, Any]]],
69
+ ) -> Iterator[str]:
70
+ """Send message to chat and stream response via SSE.
71
+
72
+ Args:
73
+ chat_id: Chat ID (UUID).
74
+ content: Message content (string or list of content blocks).
75
+
76
+ Yields:
77
+ Server-sent event data chunks.
78
+ """
79
+ return self._client._stream(
80
+ "POST",
81
+ f"/chats/{chat_id}/messages/stream",
82
+ json={"content": content}
83
+ )
84
+
85
+ def get(self, chat_id: str) -> Dict[str, Any]:
86
+ """Get a chat with all messages.
87
+
88
+ Args:
89
+ chat_id: Chat ID (UUID).
90
+
91
+ Returns:
92
+ Chat information with messages.
93
+ """
94
+ return self._client._request("GET", f"/chats/{chat_id}")
95
+
96
+ def list(self) -> List[Dict[str, Any]]:
97
+ """List all chats for the current user.
98
+
99
+ Returns:
100
+ List of chats.
101
+ """
102
+ return self._client._request("GET", "/chats")
103
+
104
+ def update(
105
+ self,
106
+ chat_id: str,
107
+ title: Optional[str] = None,
108
+ ) -> Dict[str, Any]:
109
+ """Update a chat.
110
+
111
+ Args:
112
+ chat_id: Chat ID (UUID).
113
+ title: New title.
114
+
115
+ Returns:
116
+ Updated chat information.
117
+ """
118
+ data: Dict[str, Any] = {}
119
+ if title is not None:
120
+ data["title"] = title
121
+
122
+ return self._client._request("PUT", f"/chats/{chat_id}", json=data)
123
+
124
+ def delete(self, chat_id: str) -> None:
125
+ """Delete a chat and all its messages.
126
+
127
+ Args:
128
+ chat_id: Chat ID (UUID).
129
+ """
130
+ self._client._request("DELETE", f"/chats/{chat_id}")
sinas/client.py ADDED
@@ -0,0 +1,198 @@
1
+ """SINAS SDK client."""
2
+
3
+ import os
4
+ from typing import Any, Dict, Iterator, Optional
5
+
6
+ import httpx
7
+
8
+ from sinas.auth import AuthAPI
9
+ from sinas.chats import ChatsAPI
10
+ from sinas.executions import ExecutionsAPI
11
+ from sinas.exceptions import SinasAPIError, SinasAuthError, SinasNotFoundError, SinasValidationError
12
+ from sinas.state import StateAPI
13
+ from sinas.webhooks import WebhooksAPI
14
+
15
+
16
+ class SinasClient:
17
+ """SINAS Runtime API client."""
18
+
19
+ def __init__(
20
+ self,
21
+ base_url: Optional[str] = None,
22
+ api_key: Optional[str] = None,
23
+ token: Optional[str] = None,
24
+ timeout: float = 30.0,
25
+ ) -> None:
26
+ """Initialize SINAS Runtime client.
27
+
28
+ Args:
29
+ base_url: Base URL for SINAS API. Defaults to SINAS_BASE_URL env var.
30
+ api_key: API key for authentication. Defaults to SINAS_API_KEY env var.
31
+ token: JWT token for authentication. Defaults to SINAS_TOKEN env var.
32
+ timeout: Request timeout in seconds.
33
+ """
34
+ self.base_url = (base_url or os.getenv("SINAS_BASE_URL", "")).rstrip("/")
35
+ if not self.base_url:
36
+ raise ValueError(
37
+ "base_url must be provided or SINAS_BASE_URL environment variable must be set"
38
+ )
39
+
40
+ self._api_key = api_key or os.getenv("SINAS_API_KEY")
41
+ self._token = token or os.getenv("SINAS_TOKEN")
42
+ self._timeout = timeout
43
+
44
+ self._client = httpx.Client(timeout=timeout)
45
+
46
+ # Initialize Runtime API modules
47
+ self.auth = AuthAPI(self)
48
+ self.state = StateAPI(self)
49
+ self.chats = ChatsAPI(self)
50
+ self.webhooks = WebhooksAPI(self)
51
+ self.executions = ExecutionsAPI(self)
52
+
53
+ def __enter__(self) -> "SinasClient":
54
+ return self
55
+
56
+ def __exit__(self, *args: Any) -> None:
57
+ self.close()
58
+
59
+ def close(self) -> None:
60
+ """Close the HTTP client."""
61
+ self._client.close()
62
+
63
+ def _get_headers(self) -> Dict[str, str]:
64
+ """Get headers for API requests."""
65
+ headers = {"Content-Type": "application/json"}
66
+
67
+ if self._token:
68
+ headers["Authorization"] = f"Bearer {self._token}"
69
+ elif self._api_key:
70
+ headers["Authorization"] = f"Bearer {self._api_key}"
71
+
72
+ return headers
73
+
74
+ def _request(
75
+ self,
76
+ method: str,
77
+ path: str,
78
+ json: Optional[Dict[str, Any]] = None,
79
+ params: Optional[Dict[str, Any]] = None,
80
+ ) -> Any:
81
+ """Make an HTTP request to the API.
82
+
83
+ Args:
84
+ method: HTTP method (GET, POST, PUT, DELETE, PATCH).
85
+ path: API path (without base URL).
86
+ json: JSON request body.
87
+ params: Query parameters.
88
+
89
+ Returns:
90
+ Response data.
91
+
92
+ Raises:
93
+ SinasAPIError: If the request fails.
94
+ """
95
+ url = f"{self.base_url}{path}"
96
+ headers = self._get_headers()
97
+
98
+ try:
99
+ response = self._client.request(
100
+ method=method,
101
+ url=url,
102
+ json=json,
103
+ params=params,
104
+ headers=headers,
105
+ )
106
+ self._handle_response(response)
107
+
108
+ if response.status_code == 204:
109
+ return None
110
+
111
+ return response.json()
112
+ except httpx.HTTPError as e:
113
+ raise SinasAPIError(f"HTTP request failed: {e}")
114
+
115
+ def _stream(
116
+ self,
117
+ method: str,
118
+ path: str,
119
+ json: Optional[Dict[str, Any]] = None,
120
+ ) -> Iterator[str]:
121
+ """Make a streaming HTTP request to the API.
122
+
123
+ Args:
124
+ method: HTTP method.
125
+ path: API path (without base URL).
126
+ json: JSON request body.
127
+
128
+ Yields:
129
+ Server-sent event data.
130
+
131
+ Raises:
132
+ SinasAPIError: If the request fails.
133
+ """
134
+ url = f"{self.base_url}{path}"
135
+ headers = self._get_headers()
136
+
137
+ try:
138
+ with self._client.stream(
139
+ method=method,
140
+ url=url,
141
+ json=json,
142
+ headers=headers,
143
+ ) as response:
144
+ self._handle_response(response)
145
+ for line in response.iter_lines():
146
+ if line.startswith("data: "):
147
+ yield line[6:]
148
+ except httpx.HTTPError as e:
149
+ raise SinasAPIError(f"HTTP streaming request failed: {e}")
150
+
151
+ def _handle_response(self, response: httpx.Response) -> None:
152
+ """Handle HTTP response and raise appropriate exceptions.
153
+
154
+ Args:
155
+ response: HTTP response.
156
+
157
+ Raises:
158
+ SinasAuthError: If authentication fails.
159
+ SinasNotFoundError: If resource is not found.
160
+ SinasValidationError: If validation fails.
161
+ SinasAPIError: For other API errors.
162
+ """
163
+ if response.is_success:
164
+ return
165
+
166
+ try:
167
+ error_data = response.json()
168
+ except Exception:
169
+ error_data = {}
170
+
171
+ message = error_data.get("detail", f"HTTP {response.status_code}")
172
+
173
+ if response.status_code == 401:
174
+ raise SinasAuthError(message, status_code=response.status_code, response=error_data)
175
+ elif response.status_code == 404:
176
+ raise SinasNotFoundError(message, status_code=response.status_code, response=error_data)
177
+ elif response.status_code == 422:
178
+ raise SinasValidationError(
179
+ message, status_code=response.status_code, response=error_data
180
+ )
181
+ else:
182
+ raise SinasAPIError(message, status_code=response.status_code, response=error_data)
183
+
184
+ def set_token(self, token: str) -> None:
185
+ """Set the authentication token.
186
+
187
+ Args:
188
+ token: JWT token.
189
+ """
190
+ self._token = token
191
+
192
+ def set_api_key(self, api_key: str) -> None:
193
+ """Set the API key.
194
+
195
+ Args:
196
+ api_key: API key.
197
+ """
198
+ self._api_key = api_key
sinas/exceptions.py ADDED
@@ -0,0 +1,41 @@
1
+ """SINAS SDK exceptions."""
2
+
3
+ from typing import Any, Dict, Optional
4
+
5
+
6
+ class SinasError(Exception):
7
+ """Base exception for SINAS SDK."""
8
+
9
+ pass
10
+
11
+
12
+ class SinasAPIError(SinasError):
13
+ """Exception raised for API errors."""
14
+
15
+ def __init__(
16
+ self,
17
+ message: str,
18
+ status_code: Optional[int] = None,
19
+ response: Optional[Dict[str, Any]] = None,
20
+ ) -> None:
21
+ super().__init__(message)
22
+ self.status_code = status_code
23
+ self.response = response
24
+
25
+
26
+ class SinasAuthError(SinasAPIError):
27
+ """Exception raised for authentication errors."""
28
+
29
+ pass
30
+
31
+
32
+ class SinasNotFoundError(SinasAPIError):
33
+ """Exception raised when a resource is not found."""
34
+
35
+ pass
36
+
37
+
38
+ class SinasValidationError(SinasAPIError):
39
+ """Exception raised for validation errors."""
40
+
41
+ pass
sinas/executions.py ADDED
@@ -0,0 +1,81 @@
1
+ """Runtime Executions API."""
2
+
3
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
4
+
5
+ if TYPE_CHECKING:
6
+ from sinas.client import SinasClient
7
+
8
+
9
+ class ExecutionsAPI:
10
+ """Runtime Executions API methods."""
11
+
12
+ def __init__(self, client: "SinasClient") -> None:
13
+ self._client = client
14
+
15
+ def list(
16
+ self,
17
+ function_name: Optional[str] = None,
18
+ status: Optional[str] = None,
19
+ skip: int = 0,
20
+ limit: int = 100,
21
+ ) -> List[Dict[str, Any]]:
22
+ """List function executions.
23
+
24
+ Args:
25
+ function_name: Filter by function name.
26
+ status: Filter by execution status (running, completed, failed, awaiting_input).
27
+ skip: Number of executions to skip (pagination).
28
+ limit: Maximum number of executions to return (1-1000).
29
+
30
+ Returns:
31
+ List of executions.
32
+ """
33
+ params: Dict[str, Any] = {"skip": skip, "limit": limit}
34
+ if function_name is not None:
35
+ params["function_name"] = function_name
36
+ if status is not None:
37
+ params["status"] = status
38
+
39
+ return self._client._request("GET", "/executions", params=params)
40
+
41
+ def get(self, execution_id: str) -> Dict[str, Any]:
42
+ """Get a specific execution.
43
+
44
+ Args:
45
+ execution_id: Execution ID.
46
+
47
+ Returns:
48
+ Execution details.
49
+ """
50
+ return self._client._request("GET", f"/executions/{execution_id}")
51
+
52
+ def get_steps(self, execution_id: str) -> List[Dict[str, Any]]:
53
+ """Get all steps for an execution.
54
+
55
+ Args:
56
+ execution_id: Execution ID.
57
+
58
+ Returns:
59
+ List of execution steps.
60
+ """
61
+ return self._client._request("GET", f"/executions/{execution_id}/steps")
62
+
63
+ def continue_execution(
64
+ self,
65
+ execution_id: str,
66
+ input: Dict[str, Any],
67
+ ) -> Dict[str, Any]:
68
+ """Continue a paused execution with user input.
69
+
70
+ Args:
71
+ execution_id: Execution ID.
72
+ input: User input data.
73
+
74
+ Returns:
75
+ Continuation response with execution status, output_data, or next prompt.
76
+ """
77
+ return self._client._request(
78
+ "POST",
79
+ f"/executions/{execution_id}/continue",
80
+ json={"input": input}
81
+ )
@@ -0,0 +1,20 @@
1
+ """SINAS integrations for web frameworks."""
2
+
3
+ from sinas.integrations.fastapi import SinasAuth, SinasFastAPI
4
+ from sinas.integrations.routers import (
5
+ create_chat_router,
6
+ create_executions_router,
7
+ create_runtime_router,
8
+ create_state_router,
9
+ create_webhook_router,
10
+ )
11
+
12
+ __all__ = [
13
+ "SinasAuth",
14
+ "SinasFastAPI",
15
+ "create_runtime_router",
16
+ "create_state_router",
17
+ "create_chat_router",
18
+ "create_webhook_router",
19
+ "create_executions_router",
20
+ ]