codemie-sdk-python 0.1.1__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.

Potentially problematic release.


This version of codemie-sdk-python might be problematic. Click here for more details.

@@ -0,0 +1,23 @@
1
+ """
2
+ CodeMie SDK for Python
3
+ ~~~~~~~~~~~~~~~~~~~~~
4
+
5
+ A Python SDK for interacting with CodeMie API.
6
+
7
+ Basic usage:
8
+
9
+ >>> from codemie_sdk import CodeMieClient
10
+ >>> client = CodeMieClient(
11
+ ... auth_server_url="https://auth.example.com",
12
+ ... auth_client_id="client_id",
13
+ ... auth_client_secret="secret",
14
+ ... auth_realm_name="realm",
15
+ ... codemie_api_domain="api.codemie.com"
16
+ ... )
17
+ >>> assistants = client.assistants.list()
18
+ """
19
+
20
+ from .client.client import CodeMieClient
21
+
22
+ __version__ = "0.1.11"
23
+ __all__ = ["CodeMieClient"]
@@ -0,0 +1,5 @@
1
+ """Authentication module for CodeMie SDK."""
2
+
3
+ from .credentials import KeycloakCredentials
4
+
5
+ __all__ = ["KeycloakCredentials"]
@@ -0,0 +1,112 @@
1
+ """Authentication credentials module for CodeMie SDK."""
2
+
3
+ import requests
4
+ from typing import Optional
5
+
6
+
7
+ class KeycloakCredentials:
8
+ """Keycloak authentication credentials handler."""
9
+
10
+ def __init__(
11
+ self,
12
+ server_url: str,
13
+ realm_name: str,
14
+ client_id: Optional[str] = None,
15
+ client_secret: Optional[str] = None,
16
+ username: Optional[str] = None,
17
+ password: Optional[str] = None,
18
+ verify_ssl: bool = True,
19
+ ):
20
+ """Initialize Keycloak credentials.
21
+
22
+ Args:
23
+ server_url: Keycloak server URL
24
+ realm_name: Realm name
25
+ client_id: Client ID (optional if using username/password)
26
+ client_secret: Client secret (optional if using username/password)
27
+ username: Username/email for password grant (optional)
28
+ password: Password for password grant (optional)
29
+ verify_ssl: Whether to verify SSL certificates (default: True)
30
+ """
31
+ self.server_url = server_url.rstrip("/")
32
+ self.realm_name = realm_name
33
+ self.client_id = client_id
34
+ self.client_secret = client_secret
35
+ self.username = username
36
+ self.password = password
37
+ self.verify_ssl = verify_ssl
38
+
39
+ if not ((client_id and client_secret) or (username and password)):
40
+ raise ValueError(
41
+ "Either client credentials (client_id, client_secret) or "
42
+ "user credentials (username, password) must be provided"
43
+ )
44
+
45
+ def get_token(self) -> str:
46
+ """Get access token using either client credentials or password grant."""
47
+ url = (
48
+ f"{self.server_url}/realms/{self.realm_name}/protocol/openid-connect/token"
49
+ )
50
+
51
+ if self.username and self.password:
52
+ # Use Resource Owner Password Credentials flow
53
+ payload = {
54
+ "grant_type": "password",
55
+ "username": self.username,
56
+ "password": self.password,
57
+ "client_id": self.client_id
58
+ or "codemie-sdk", # Use default client if not specified
59
+ }
60
+ if self.client_secret:
61
+ payload["client_secret"] = self.client_secret
62
+ else:
63
+ # Use Client Credentials flow
64
+ payload = {
65
+ "grant_type": "client_credentials",
66
+ "client_id": self.client_id,
67
+ "client_secret": self.client_secret,
68
+ }
69
+
70
+ response = requests.post(url, data=payload, verify=self.verify_ssl)
71
+ response.raise_for_status()
72
+ return response.json()["access_token"]
73
+
74
+ def exchange_token_for_user(self, email: str, access_token: str) -> str:
75
+ """Exchange service account token for user token."""
76
+ user_id = self.find_user_by_email(email, access_token)
77
+ return self._exchange_token_for_user(user_id, access_token)
78
+
79
+ def find_user_by_email(self, email: str, access_token: str) -> str:
80
+ """Find user ID by email."""
81
+ url = f"{self.server_url}/admin/realms/{self.realm_name}/users?email={email}"
82
+ headers = {
83
+ "Authorization": f"Bearer {access_token}",
84
+ "Content-Type": "application/json",
85
+ }
86
+ response = requests.get(url, headers=headers, verify=self.verify_ssl)
87
+ response.raise_for_status()
88
+
89
+ users = response.json()
90
+ if not users:
91
+ raise ValueError(f"User with email {email} not found")
92
+ return users[0]["id"]
93
+
94
+ def _exchange_token_for_user(self, user_id: str, service_account_token: str) -> str:
95
+ """Exchange token for specific user."""
96
+ url = (
97
+ f"{self.server_url}/realms/{self.realm_name}/protocol/openid-connect/token"
98
+ )
99
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
100
+ payload = {
101
+ "client_id": self.client_id,
102
+ "client_secret": self.client_secret,
103
+ "grant_type": "urn:ietf:params:oauth:grant-datasource_type:token-exchange",
104
+ "subject_token": service_account_token,
105
+ "requested_subject": user_id,
106
+ }
107
+
108
+ response = requests.post(
109
+ url, headers=headers, data=payload, verify=self.verify_ssl
110
+ )
111
+ response.raise_for_status()
112
+ return response.json()["access_token"]
@@ -0,0 +1,5 @@
1
+ """Client module for CodeMie SDK."""
2
+
3
+ from .client import CodeMieClient
4
+
5
+ __all__ = ["CodeMieClient"]
@@ -0,0 +1,107 @@
1
+ """Base client implementation for CodeMie SDK."""
2
+
3
+ from typing import Optional
4
+
5
+ from ..auth.credentials import KeycloakCredentials
6
+ from ..services.assistant import AssistantService
7
+ from ..services.datasource import DatasourceService
8
+ from ..services.llm import LLMService
9
+ from ..services.integration import IntegrationService
10
+ from ..services.task import TaskService
11
+ from ..services.user import UserService
12
+ from ..services.workflow import WorkflowService
13
+
14
+
15
+ class CodeMieClient:
16
+ """Main client class for interacting with CodeMie API."""
17
+
18
+ def __init__(
19
+ self,
20
+ auth_server_url: str,
21
+ auth_realm_name: str,
22
+ codemie_api_domain: str,
23
+ auth_client_id: Optional[str] = None,
24
+ auth_client_secret: Optional[str] = None,
25
+ username: Optional[str] = None,
26
+ password: Optional[str] = None,
27
+ verify_ssl: bool = True,
28
+ ):
29
+ """Initialize CodeMie client with authentication credentials.
30
+
31
+ Args:
32
+ auth_server_url: Keycloak server URL
33
+ auth_realm_name: Realm name for authentication
34
+ codemie_api_domain: CodeMie API domain
35
+ auth_client_id: Client ID for authentication (optional if using username/password)
36
+ auth_client_secret: Client secret for authentication (optional if using username/password)
37
+ username: Username/email for password grant (optional if using client credentials)
38
+ password: Password for password grant (optional if using client credentials)
39
+ verify_ssl: Whether to verify SSL certificates (default: True)
40
+ """
41
+ self.auth = KeycloakCredentials(
42
+ server_url=auth_server_url,
43
+ realm_name=auth_realm_name,
44
+ client_id=auth_client_id,
45
+ client_secret=auth_client_secret,
46
+ username=username,
47
+ password=password,
48
+ verify_ssl=verify_ssl,
49
+ )
50
+
51
+ self._token: Optional[str] = None
52
+ self._api_domain = codemie_api_domain.rstrip("/")
53
+ self._verify_ssl = verify_ssl
54
+
55
+ # Initialize token first
56
+ self._token = self.auth.get_token()
57
+
58
+ # Initialize services with verify_ssl parameter and token
59
+ self.assistants = AssistantService(
60
+ self._api_domain, self._token, verify_ssl=verify_ssl
61
+ )
62
+ self.llms = LLMService(self._api_domain, self._token, verify_ssl=verify_ssl)
63
+ self.integrations = IntegrationService(
64
+ self._api_domain, self._token, verify_ssl=verify_ssl
65
+ )
66
+ self.tasks = TaskService(self._api_domain, self._token, verify_ssl=verify_ssl)
67
+ self.users = UserService(self._api_domain, self._token, verify_ssl=verify_ssl)
68
+ self.datasources = DatasourceService(
69
+ self._api_domain, self._token, verify_ssl=verify_ssl
70
+ )
71
+ self.workflows = WorkflowService(
72
+ self._api_domain, self._token, verify_ssl=self._verify_ssl
73
+ )
74
+
75
+ @property
76
+ def token(self) -> str:
77
+ """Get current token or fetch new one if not available."""
78
+ if not self._token:
79
+ self._token = self.auth.get_token()
80
+ return self._token
81
+
82
+ def refresh_token(self) -> str:
83
+ """Force token refresh."""
84
+ self._token = self.auth.get_token()
85
+ # Update token in services
86
+ self.assistants = AssistantService(
87
+ self._api_domain, self._token, verify_ssl=self._verify_ssl
88
+ )
89
+ self.llms = LLMService(
90
+ self._api_domain, self._token, verify_ssl=self._verify_ssl
91
+ )
92
+ self.integrations = IntegrationService(
93
+ self._api_domain, self._token, verify_ssl=self._verify_ssl
94
+ )
95
+ self.tasks = TaskService(
96
+ self._api_domain, self._token, verify_ssl=self._verify_ssl
97
+ )
98
+ self.users = UserService(
99
+ self._api_domain, self._token, verify_ssl=self._verify_ssl
100
+ )
101
+ self.datasources = DatasourceService(
102
+ self._api_domain, self._token, verify_ssl=self._verify_ssl
103
+ )
104
+ self.workflows = WorkflowService(
105
+ self._api_domain, self._token, verify_ssl=self._verify_ssl
106
+ )
107
+ return self._token
@@ -0,0 +1,45 @@
1
+ """Custom exceptions for the CodeMie SDK."""
2
+
3
+ from typing import Optional
4
+
5
+
6
+ class CodeMieError(Exception):
7
+ """Base exception for all CodeMie SDK errors."""
8
+
9
+ pass
10
+
11
+
12
+ class ApiError(CodeMieError):
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] = None,
20
+ ):
21
+ """Initialize API error.
22
+
23
+ Args:
24
+ message: Error message
25
+ status_code: HTTP status code if applicable
26
+ response: Raw API response if available
27
+ """
28
+ self.status_code = status_code
29
+ self.response = response
30
+ super().__init__(message)
31
+
32
+
33
+ class NotFoundError(ApiError):
34
+ """Exception raised when a resource is not found."""
35
+
36
+ def __init__(self, resource_type: str, resource_id: str):
37
+ """Initialize not found error.
38
+
39
+ Args:
40
+ resource_type: Type of resource that was not found (e.g., "Integration")
41
+ resource_id: ID or identifier of the resource
42
+ """
43
+ super().__init__(
44
+ message=f"{resource_type} with {resource_id} not found", status_code=404
45
+ )
@@ -0,0 +1,192 @@
1
+ """Models for assistant-related data structures."""
2
+
3
+ import uuid
4
+ from datetime import datetime
5
+ from enum import Enum
6
+ from typing import List, Optional, Any, Union
7
+
8
+ from pydantic import BaseModel, Field, ConfigDict
9
+
10
+ from .common import User
11
+ from .integration import Integration
12
+
13
+
14
+ class ToolDetails(BaseModel):
15
+ """Model for tool details."""
16
+
17
+ model_config = ConfigDict(extra="ignore")
18
+
19
+ name: str
20
+ label: Optional[str] = None
21
+ settings_config: bool = False
22
+ user_description: Optional[str] = None
23
+ settings: Optional[Integration] = None
24
+
25
+
26
+ class ToolKitDetails(BaseModel):
27
+ """Model for toolkit details."""
28
+
29
+ model_config = ConfigDict(extra="ignore")
30
+
31
+ toolkit: str
32
+ tools: List[ToolDetails]
33
+ label: str = ""
34
+ settings_config: bool = False
35
+ is_external: bool = False
36
+ settings: Optional[Integration] = None
37
+
38
+
39
+ class ContextType(str, Enum):
40
+ """Enum for context types."""
41
+
42
+ KNOWLEDGE_BASE = "knowledge_base"
43
+ CODE = "code"
44
+
45
+
46
+ class Context(BaseModel):
47
+ """Model for context configuration."""
48
+
49
+ model_config = ConfigDict(extra="ignore")
50
+
51
+ context_type: ContextType
52
+ name: str
53
+
54
+
55
+ class SystemPromptHistory(BaseModel):
56
+ """Model for system prompt history."""
57
+
58
+ model_config = ConfigDict(extra="ignore")
59
+
60
+ system_prompt: str
61
+ date: datetime
62
+ created_by: Optional[User] = None
63
+
64
+
65
+ class AssistantBase(BaseModel):
66
+ """Base model for assistant with common fields."""
67
+
68
+ model_config = ConfigDict(extra="ignore")
69
+
70
+ id: Optional[str] = None
71
+ created_by: Optional[User] = None
72
+ name: str
73
+ description: str
74
+ icon_url: Optional[str] = None
75
+
76
+
77
+ class Assistant(AssistantBase):
78
+ """Full assistant model with additional fields."""
79
+
80
+ model_config = ConfigDict(extra="ignore", use_enum_values=True)
81
+
82
+ system_prompt: str
83
+ system_prompt_history: List[SystemPromptHistory] = Field(default_factory=list)
84
+ project: str
85
+ llm_model_type: Optional[str] = None
86
+ toolkits: List[ToolKitDetails] = Field(default_factory=list)
87
+ user_prompts: List[str] = Field(default_factory=list)
88
+ shared: bool = False
89
+ is_react: bool = False
90
+ is_global: bool = False
91
+ created_date: Optional[datetime] = None
92
+ updated_date: Optional[datetime] = None
93
+ creator: str = "system"
94
+ slug: Optional[str] = None
95
+ temperature: Optional[float] = None
96
+ top_p: Optional[float] = None
97
+ context: List[Context] = Field(default_factory=list)
98
+ user_abilities: Optional[List[Any]] = None
99
+
100
+
101
+ class AssistantRequestBase(AssistantBase):
102
+ """Base model for assistant requests with common request fields."""
103
+
104
+ model_config = ConfigDict(extra="ignore", use_enum_values=True)
105
+
106
+ system_prompt: str
107
+ project: str
108
+ context: List[Context] = Field(default_factory=list)
109
+ llm_model_type: str
110
+ toolkits: List[ToolKitDetails]
111
+ user_prompts: List[str] = Field(default_factory=list)
112
+ shared: bool = False
113
+ is_react: bool = False
114
+ is_global: Optional[bool] = False
115
+ slug: Optional[str] = None
116
+ temperature: Optional[float] = None
117
+ top_p: Optional[float] = None
118
+
119
+
120
+ class AssistantCreateRequest(AssistantRequestBase):
121
+ """Model for creating a new assistant."""
122
+
123
+ pass
124
+
125
+
126
+ class AssistantUpdateRequest(AssistantRequestBase):
127
+ """Model for updating an existing assistant."""
128
+
129
+ pass
130
+
131
+
132
+ class ChatRole(str, Enum):
133
+ """Enum for chat message roles."""
134
+
135
+ ASSISTANT = "Assistant"
136
+ USER = "User"
137
+
138
+
139
+ class ChatMessage(BaseModel):
140
+ """Model for chat message."""
141
+
142
+ role: ChatRole
143
+ message: Optional[str] = Field(default="")
144
+
145
+
146
+ class AssistantChatRequest(BaseModel):
147
+ """Model for chat request to assistant."""
148
+
149
+ conversation_id: Optional[str] = Field(
150
+ default=str(uuid.uuid4()), description="Conversation identifier"
151
+ )
152
+ text: Optional[str] = Field(default=None, description="User's input error_message")
153
+ content_raw: Optional[str] = Field(default="", description="Raw content input")
154
+ file_name: Optional[str] = Field(default=None, description="Associated file name")
155
+ llm_model: Optional[str] = Field(
156
+ default=None, description="Specific LLM model to use"
157
+ )
158
+ history: Union[List[ChatMessage], str] = Field(
159
+ default_factory=list,
160
+ description="Conversation history as list of messages or string",
161
+ )
162
+ history_index: int = Field(
163
+ default=0, description="DataSource in conversation history"
164
+ )
165
+ stream: bool = Field(default=False, description="Enable streaming response")
166
+ top_k: int = Field(default=10, description="Top K results to consider")
167
+ system_prompt: str = Field(default="", description="Override system prompt")
168
+ background_task: bool = Field(default=False, description="Run as background task")
169
+
170
+
171
+ class BaseModelResponse(BaseModel):
172
+ """Model for chat response from assistant."""
173
+
174
+ generated: str = Field(description="Generated response error_message")
175
+ time_elapsed: Optional[float] = Field(
176
+ default=None, alias="timeElapsed", description="Time taken for generation"
177
+ )
178
+ tokens_used: Optional[int] = Field(
179
+ default=None, alias="tokensUsed", description="Number of tokens used"
180
+ )
181
+ thoughts: Optional[List[dict]] = Field(
182
+ default=None, description="Thought process details"
183
+ )
184
+ task_id: Optional[str] = Field(
185
+ default=None, alias="taskId", description="Background task identifier"
186
+ )
187
+
188
+ class Config:
189
+ # Allow population by field name as well as alias
190
+ populate_by_name = True
191
+ # Preserve alias on export
192
+ alias_generator = None
@@ -0,0 +1,39 @@
1
+ from typing import Optional, Dict, Any
2
+
3
+ from pydantic import BaseModel, ConfigDict, Field, model_validator
4
+
5
+
6
+ class TokensUsage(BaseModel):
7
+ input_tokens: Optional[int] = None
8
+ output_tokens: Optional[int] = None
9
+ money_spent: Optional[float] = None
10
+
11
+ model_config = ConfigDict(extra="ignore")
12
+
13
+
14
+ class User(BaseModel):
15
+ """Model representing the User."""
16
+
17
+ model_config = ConfigDict(extra="ignore")
18
+
19
+ user_id: str = Field(None)
20
+ username: str = ""
21
+ name: str = ""
22
+
23
+ @model_validator(mode="before")
24
+ def before_init(cls, values):
25
+ # Check if 'id' is present, then set 'id' field for correct deserialization
26
+ if "id" in values:
27
+ values["user_id"] = values.pop("id")
28
+ return values
29
+
30
+
31
+ class PaginationParams(BaseModel):
32
+ """Pagination parameters with validation."""
33
+
34
+ page: int = Field(..., ge=0, description="Page number (0-based)")
35
+ per_page: int = Field(..., gt=0, description="Number of items per page")
36
+
37
+ def to_dict(self) -> Dict[str, Any]:
38
+ """Convert pagination parameters to dictionary."""
39
+ return self.model_dump(exclude_none=True)