magickmind 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.
Files changed (43) hide show
  1. magick_mind/__init__.py +39 -0
  2. magick_mind/auth/__init__.py +9 -0
  3. magick_mind/auth/base.py +46 -0
  4. magick_mind/auth/email_password.py +268 -0
  5. magick_mind/client.py +188 -0
  6. magick_mind/config.py +28 -0
  7. magick_mind/exceptions.py +107 -0
  8. magick_mind/http/__init__.py +5 -0
  9. magick_mind/http/client.py +313 -0
  10. magick_mind/models/__init__.py +17 -0
  11. magick_mind/models/auth.py +30 -0
  12. magick_mind/models/common.py +32 -0
  13. magick_mind/models/errors.py +73 -0
  14. magick_mind/models/v1/__init__.py +83 -0
  15. magick_mind/models/v1/api_keys.py +115 -0
  16. magick_mind/models/v1/artifact.py +151 -0
  17. magick_mind/models/v1/chat.py +104 -0
  18. magick_mind/models/v1/corpus.py +82 -0
  19. magick_mind/models/v1/end_user.py +75 -0
  20. magick_mind/models/v1/history.py +94 -0
  21. magick_mind/models/v1/mindspace.py +130 -0
  22. magick_mind/models/v1/model.py +25 -0
  23. magick_mind/models/v1/project.py +73 -0
  24. magick_mind/realtime/__init__.py +5 -0
  25. magick_mind/realtime/client.py +202 -0
  26. magick_mind/realtime/handler.py +122 -0
  27. magick_mind/resources/README.md +201 -0
  28. magick_mind/resources/__init__.py +42 -0
  29. magick_mind/resources/base.py +31 -0
  30. magick_mind/resources/v1/__init__.py +19 -0
  31. magick_mind/resources/v1/api_keys.py +181 -0
  32. magick_mind/resources/v1/artifact.py +287 -0
  33. magick_mind/resources/v1/chat.py +120 -0
  34. magick_mind/resources/v1/corpus.py +156 -0
  35. magick_mind/resources/v1/end_user.py +181 -0
  36. magick_mind/resources/v1/history.py +88 -0
  37. magick_mind/resources/v1/mindspace.py +331 -0
  38. magick_mind/resources/v1/model.py +19 -0
  39. magick_mind/resources/v1/project.py +155 -0
  40. magick_mind/routes.py +76 -0
  41. magickmind-0.1.1.dist-info/METADATA +593 -0
  42. magickmind-0.1.1.dist-info/RECORD +43 -0
  43. magickmind-0.1.1.dist-info/WHEEL +4 -0
@@ -0,0 +1,331 @@
1
+ """V1 mindspace resource implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Optional
6
+
7
+ from magick_mind.models.v1.mindspace import (
8
+ AddMindSpaceUsersRequest,
9
+ CreateMindSpaceRequest,
10
+ GetMindSpaceListResponse,
11
+ MindSpace,
12
+ MindSpaceType,
13
+ MindspaceMessagesResponse,
14
+ UpdateMindSpaceRequest,
15
+ )
16
+ from magick_mind.resources.base import BaseResource
17
+ from magick_mind.routes import Routes
18
+
19
+ if TYPE_CHECKING:
20
+ from magick_mind.http import HTTPClient
21
+
22
+
23
+ class MindspaceResourceV1(BaseResource):
24
+ """
25
+ Mindspace resource client for V1 API.
26
+
27
+ Provides typed interface for managing mindspaces (organizational containers
28
+ for chat conversations, corpus, and users).
29
+
30
+ Example:
31
+ # Create a private mindspace
32
+ mindspace = client.v1.mindspace.create(
33
+ name="My Workspace",
34
+ type="private",
35
+ description="Personal workspace",
36
+ corpus_ids=["corp-123"]
37
+ )
38
+
39
+ # List all mindspaces
40
+ mindspaces = client.v1.mindspace.list(user_id="user-456")
41
+
42
+ # Get messages from mindspace
43
+ messages = client.v1.mindspace.get_messages(
44
+ mindspace_id="mind-123",
45
+ limit=50
46
+ )
47
+ """
48
+
49
+ def create(
50
+ self,
51
+ name: str,
52
+ type: MindSpaceType,
53
+ description: Optional[str] = None,
54
+ project_id: Optional[str] = None,
55
+ corpus_ids: Optional[list[str]] = None,
56
+ user_ids: Optional[list[str]] = None,
57
+ ) -> MindSpace:
58
+ """
59
+ Create a new mindspace.
60
+ Args:
61
+ name: Mindspace name (max 100 characters)
62
+ type: Mindspace type - either "PRIVATE" or "GROUP"
63
+ description: Optional description (max 256 characters)
64
+ project_id: Optional associated project ID
65
+ corpus_ids: Optional list of corpus IDs to attach
66
+ user_ids: Optional list of user IDs to grant access
67
+
68
+ Returns:
69
+ MindSpace
70
+
71
+ Raises:
72
+ HTTPError: If the API request fails
73
+ ValidationError: If parameters are invalid
74
+
75
+ Example:
76
+ # Create a group mindspace
77
+ mindspace = client.v1.mindspace.create(
78
+ name="Engineering Team",
79
+ type="GROUP",
80
+ description="Team collaboration space",
81
+ corpus_ids=["corp-1", "corp-2"],
82
+ user_ids=["user-1", "user-2"]
83
+ )
84
+ print(f"Created mindspace: {mindspace.id}")
85
+ """
86
+ # Build and validate request
87
+ request = CreateMindSpaceRequest(
88
+ name=name,
89
+ type=type,
90
+ description=description,
91
+ project_id=project_id,
92
+ corpus_ids=corpus_ids or [],
93
+ user_ids=user_ids or [],
94
+ )
95
+
96
+ # Make API call
97
+ response = self._http.post(
98
+ Routes.MINDSPACES, json=request.model_dump(exclude_none=True)
99
+ )
100
+
101
+ return MindSpace.model_validate(response)
102
+
103
+ def get(self, mindspace_id: str) -> MindSpace:
104
+ """
105
+ Get a mindspace by ID.
106
+
107
+ Args:
108
+ mindspace_id: Mindspace ID to retrieve
109
+
110
+ Returns:
111
+ MindSpace
112
+
113
+ Raises:
114
+ HTTPError: If the API request fails or mindspace not found
115
+
116
+ Example:
117
+ mindspace = client.v1.mindspace.get("mind-123")
118
+ print(f"Mindspace: {mindspace.name}")
119
+ print(f"Type: {mindspace.type}")
120
+ print(f"Corpus: {mindspace.corpus_ids}")
121
+ """
122
+ response_data = self._http.get(Routes.mindspace(mindspace_id))
123
+ return MindSpace.model_validate(response_data)
124
+
125
+ def list(self, user_id: Optional[str] = None) -> GetMindSpaceListResponse:
126
+ """
127
+ List mindspaces, optionally filtered by user.
128
+
129
+ Args:
130
+ user_id: Optional user ID to filter mindspaces
131
+
132
+ Returns:
133
+ GetMindSpaceListResponse with list of mindspaces
134
+
135
+ Raises:
136
+ HTTPError: If the API request fails
137
+
138
+ Example:
139
+ # List all mindspaces for a user
140
+ response = client.v1.mindspace.list(user_id="user-456")
141
+ for ms in response.mindspaces:
142
+ print(f"- {ms.name} ({ms.type})")
143
+ """
144
+ params = {}
145
+ if user_id:
146
+ params["user_id"] = user_id
147
+
148
+ response_data = self._http.get(Routes.MINDSPACES, params=params)
149
+ return GetMindSpaceListResponse.model_validate(response_data)
150
+
151
+ def update(
152
+ self,
153
+ mindspace_id: str,
154
+ name: str,
155
+ description: Optional[str] = None,
156
+ project_id: Optional[str] = None,
157
+ corpus_ids: Optional[list[str]] = None,
158
+ user_ids: Optional[list[str]] = None,
159
+ ) -> MindSpace:
160
+ """
161
+ Update an existing mindspace.
162
+
163
+ Args:
164
+ mindspace_id: Mindspace ID to update
165
+ name: Updated mindspace name (max 100 characters)
166
+ description: Updated description (max 256 characters)
167
+ project_id: Updated associated project ID
168
+ corpus_ids: Updated list of corpus IDs
169
+ user_ids: Updated list of user IDs
170
+
171
+ Returns:
172
+ MindSpace
173
+
174
+ Raises:
175
+ HTTPError: If the API request fails or mindspace not found
176
+ ValidationError: If parameters are invalid
177
+
178
+ Example:
179
+ # Update mindspace to add more corpus
180
+ mindspace = client.v1.mindspace.update(
181
+ mindspace_id="mind-123",
182
+ name="Engineering Team",
183
+ corpus_ids=["corp-1", "corp-2", "corp-3"]
184
+ )
185
+ print(f"Updated: {mindspace.corpus_ids}")
186
+ """
187
+ # Build and validate request
188
+ request = UpdateMindSpaceRequest(
189
+ name=name,
190
+ description=description,
191
+ project_id=project_id,
192
+ corpus_ids=corpus_ids or [],
193
+ user_ids=user_ids or [],
194
+ )
195
+
196
+ # Make API call
197
+ response = self._http.put(
198
+ Routes.mindspace(mindspace_id),
199
+ json=request.model_dump(exclude_none=True),
200
+ )
201
+
202
+ # Parse and validate response
203
+ return MindSpace.model_validate(response)
204
+
205
+ def delete(self, mindspace_id: str) -> None:
206
+ """
207
+ Delete a mindspace.
208
+
209
+ Args:
210
+ mindspace_id: Mindspace ID to delete
211
+
212
+ Raises:
213
+ HTTPError: If the API request fails or mindspace not found
214
+
215
+ Example:
216
+ client.v1.mindspace.delete("mind-123")
217
+ print("Mindspace deleted successfully")
218
+ """
219
+ self._http.delete(Routes.mindspace(mindspace_id))
220
+
221
+ def get_messages(
222
+ self,
223
+ mindspace_id: str,
224
+ after_id: Optional[str] = None,
225
+ before_id: Optional[str] = None,
226
+ limit: int = 50,
227
+ ) -> MindspaceMessagesResponse:
228
+ """
229
+ Fetch chat messages from a mindspace with keyset pagination.
230
+
231
+ Three modes based on parameters:
232
+ - Latest: Just mindspace_id + limit (most recent messages)
233
+ - Forward: mindspace_id + after_id + limit (messages after a point)
234
+ - Backward: mindspace_id + before_id + limit (messages before a point)
235
+
236
+ Args:
237
+ mindspace_id: Mindspace to fetch messages from
238
+ after_id: Get messages after this message ID (forward pagination)
239
+ before_id: Get messages before this message ID (backward pagination)
240
+ limit: Maximum number of messages to return (default: 50)
241
+
242
+ Returns:
243
+ MindspaceMessagesResponse with messages and pagination cursors
244
+
245
+ Raises:
246
+ ValueError: If both after_id and before_id are provided
247
+ HTTPError: If the API request fails
248
+
249
+ Example:
250
+ # Get latest 50 messages
251
+ messages = client.v1.mindspace.get_messages(
252
+ mindspace_id="mind-123"
253
+ )
254
+ for msg in messages.chat_histories:
255
+ print(f"{msg.sent_by_user_id}: {msg.content}")
256
+
257
+ # Forward pagination (newer messages)
258
+ if messages.has_more:
259
+ more = client.v1.mindspace.get_messages(
260
+ mindspace_id="mind-123",
261
+ after_id=messages.next_after_id,
262
+ limit=50
263
+ )
264
+
265
+ # Backward pagination (older messages)
266
+ if messages.has_older:
267
+ older = client.v1.mindspace.get_messages(
268
+ mindspace_id="mind-123",
269
+ before_id=messages.next_before_id,
270
+ limit=50
271
+ )
272
+ """
273
+ # Validate mutually exclusive parameters
274
+ if after_id and before_id:
275
+ raise ValueError("Cannot specify both after_id and before_id")
276
+
277
+ # Build query parameters
278
+ params = {
279
+ "mindspace_id": mindspace_id,
280
+ "limit": limit,
281
+ }
282
+
283
+ if after_id:
284
+ params["after_id"] = after_id
285
+ if before_id:
286
+ params["before_id"] = before_id
287
+
288
+ # Make request
289
+ response_data = self._http.get(Routes.MINDSPACE_MESSAGES, params=params)
290
+
291
+ # Parse and return
292
+ return MindspaceMessagesResponse.model_validate(response_data)
293
+
294
+ def add_users(
295
+ self,
296
+ mindspace_id: str,
297
+ user_ids: list[str],
298
+ ) -> MindSpace:
299
+ """
300
+ Add users to an existing mindspace.
301
+
302
+ Args:
303
+ mindspace_id: Mindspace ID to add users to
304
+ user_ids: List of user IDs to add to the mindspace
305
+
306
+ Returns:
307
+ MindSpace with updated user list
308
+
309
+ Raises:
310
+ HTTPError: If the API request fails or mindspace not found
311
+ ValidationError: If parameters are invalid
312
+
313
+ Example:
314
+ # Add users to a group mindspace
315
+ mindspace = client.v1.mindspace.add_users(
316
+ mindspace_id="mind-123",
317
+ user_ids=["user-3", "user-4"]
318
+ )
319
+ print(f"Updated users: {mindspace.user_ids}")
320
+ """
321
+ # Build and validate request
322
+ request = AddMindSpaceUsersRequest(user_ids=user_ids)
323
+
324
+ # Make API call
325
+ response = self._http.post(
326
+ Routes.mindspace_users(mindspace_id),
327
+ json=request.model_dump(exclude_none=True),
328
+ )
329
+
330
+ # Parse and validate response
331
+ return MindSpace.model_validate(response)
@@ -0,0 +1,19 @@
1
+ from typing import Optional, List
2
+
3
+ from magick_mind.resources.base import BaseResource
4
+ from magick_mind.models.v1.model import ModelsListResponse, Model
5
+ from magick_mind.routes import Routes
6
+
7
+
8
+ class ModelsResourceV1(BaseResource):
9
+ """Resource to interact with the models API."""
10
+
11
+ def list(self) -> List[Model]:
12
+ """
13
+ List all available models.
14
+
15
+ Returns:
16
+ List[Model]: A list of available models.
17
+ """
18
+ response = ModelsListResponse(**self._http.get(Routes.MODELS))
19
+ return response.data
@@ -0,0 +1,155 @@
1
+ """
2
+ Project resource for Magick Mind SDK v1 API.
3
+
4
+ Provides methods for CRUD operations on projects in the agentic SaaS backend.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Optional
10
+
11
+
12
+ from magick_mind.models.v1.project import (
13
+ CreateProjectRequest,
14
+ GetProjectListResponse,
15
+ Project,
16
+ UpdateProjectRequest,
17
+ )
18
+ from magick_mind.resources.base import BaseResource
19
+ from magick_mind.routes import Routes
20
+
21
+
22
+ class ProjectResourceV1(BaseResource):
23
+ """
24
+ Project resource for managing agentic SaaS projects.
25
+
26
+ Projects organize corpus and other resources for multi-tenant backends.
27
+ """
28
+
29
+ def create(
30
+ self,
31
+ name: str,
32
+ description: str = "",
33
+ corpus_ids: Optional[list[str]] = None,
34
+ ) -> Project:
35
+ """
36
+ Create a new project.
37
+
38
+ Args:
39
+ name: Project name (required)
40
+ description: Project description (optional, max 256 chars)
41
+ corpus_ids: List of corpus IDs to associate with project
42
+
43
+ Returns:
44
+ Created Project object
45
+
46
+ Example:
47
+ project = client.v1.project.create(
48
+ name="My Agentic App",
49
+ description="An AI-powered assistant",
50
+ corpus_ids=["corpus-123"]
51
+ )
52
+ print(f"Created project: {project.id}")
53
+ """
54
+ request = CreateProjectRequest(
55
+ name=name,
56
+ description=description,
57
+ corpus_ids=corpus_ids or [],
58
+ )
59
+
60
+ response = self._http.post(Routes.PROJECTS, json=request.model_dump())
61
+ return Project.model_validate(response)
62
+
63
+ def get(self, project_id: str) -> Project:
64
+ """
65
+ Get a project by ID.
66
+
67
+ Args:
68
+ project_id: The project ID to retrieve
69
+
70
+ Returns:
71
+ Project object
72
+
73
+ Example:
74
+ project = client.v1.project.get(project_id="proj-123")
75
+ print(f"Project name: {project.name}")
76
+ """
77
+ response = self._http.get(Routes.project(project_id))
78
+ return Project.model_validate(response)
79
+
80
+ def list(self, created_by_user_id: Optional[str] = None) -> list[Project]:
81
+ """
82
+ List projects, optionally filtered by creator user ID.
83
+
84
+ Args:
85
+ created_by_user_id: Optional user ID to filter projects created by this user
86
+
87
+ Returns:
88
+ List of Project objects
89
+
90
+ Example:
91
+ # List all accessible projects
92
+ projects = client.v1.project.list()
93
+
94
+ # List projects created by specific user
95
+ user_projects = client.v1.project.list(created_by_user_id="user-123")
96
+ for project in user_projects:
97
+ print(f"- {project.name}")
98
+ """
99
+ params = {}
100
+ if created_by_user_id:
101
+ params["user_id"] = created_by_user_id
102
+
103
+ response = self._http.get(Routes.PROJECTS, params=params)
104
+ list_response = GetProjectListResponse.model_validate(response)
105
+ return [Project.model_validate(p) for p in list_response.data]
106
+
107
+ def update(
108
+ self,
109
+ project_id: str,
110
+ name: str,
111
+ description: str = "",
112
+ corpus_ids: Optional[list[str]] = None,
113
+ ) -> Project:
114
+ """
115
+ Update an existing project.
116
+
117
+ Args:
118
+ project_id: The project ID to update
119
+ name: New project name (required)
120
+ description: New project description (optional, max 256 chars)
121
+ corpus_ids: New list of corpus IDs to associate with project
122
+
123
+ Returns:
124
+ Updated Project object
125
+
126
+ Example:
127
+ updated = client.v1.project.update(
128
+ project_id="proj-123",
129
+ name="Updated Name",
130
+ description="Updated description",
131
+ corpus_ids=["corpus-123", "corpus-456"]
132
+ )
133
+ print(f"Updated project: {updated.name}")
134
+ """
135
+ request = UpdateProjectRequest(
136
+ name=name,
137
+ description=description,
138
+ corpus_ids=corpus_ids or [],
139
+ )
140
+
141
+ response = self._http.put(Routes.project(project_id), json=request.model_dump())
142
+ return Project(**response)
143
+
144
+ def delete(self, project_id: str) -> None:
145
+ """
146
+ Delete a project.
147
+
148
+ Args:
149
+ project_id: The project ID to delete
150
+
151
+ Example:
152
+ client.v1.project.delete(project_id="proj-123")
153
+ print("Project deleted successfully")
154
+ """
155
+ self._http.delete(Routes.project(project_id))
magick_mind/routes.py ADDED
@@ -0,0 +1,76 @@
1
+ """Centralized API route constants.
2
+
3
+ All API endpoint paths are defined here to ensure consistency
4
+ across the SDK and make updates easier when the API changes.
5
+ """
6
+
7
+
8
+ class Routes:
9
+ """API v1 route paths for Bifrost."""
10
+
11
+ # Auth endpoints
12
+ AUTH_LOGIN = "/v1/auth/login"
13
+ AUTH_REFRESH = "/v1/auth/refresh"
14
+
15
+ # Chat endpoints
16
+ CHAT = "/v1/chat/magickmind"
17
+ MODELS = "/v1/models"
18
+
19
+ # Mindspace endpoints
20
+ MINDSPACES = "/v1/mindspaces"
21
+ MINDSPACE_MESSAGES = "/v1/mindspaces/messages"
22
+
23
+ @staticmethod
24
+ def mindspace(mindspace_id: str) -> str:
25
+ """Get path for a specific mindspace."""
26
+ return f"/v1/mindspaces/{mindspace_id}"
27
+
28
+ @staticmethod
29
+ def mindspace_users(mindspace_id: str) -> str:
30
+ """Get path to add users to a specific mindspace."""
31
+ return f"/v1/mindspaces/{mindspace_id}/users"
32
+
33
+ # Project endpoints
34
+ PROJECTS = "/v1/projects"
35
+
36
+ @staticmethod
37
+ def project(project_id: str) -> str:
38
+ """Get path for a specific project."""
39
+ return f"/v1/projects/{project_id}"
40
+
41
+ # End User endpoints
42
+ END_USERS = "/v1/end-users"
43
+
44
+ @staticmethod
45
+ def end_user(end_user_id: str) -> str:
46
+ """Get path for a specific end user."""
47
+ return f"/v1/end-users/{end_user_id}"
48
+
49
+ # Corpus endpoints
50
+ CORPUS = "/v1/corpus"
51
+
52
+ @staticmethod
53
+ def corpus(corpus_id: str) -> str:
54
+ """Get path for a specific corpus."""
55
+ return f"/v1/corpus/{corpus_id}"
56
+
57
+ @staticmethod
58
+ def corpus_artifacts_finalize(corpus_id: str) -> str:
59
+ """Get path for corpus artifact finalization."""
60
+ return f"/v1/corpus/{corpus_id}/artifacts/finalize"
61
+
62
+ # Artifact endpoints
63
+ ARTIFACTS = "/v1/artifacts"
64
+ ARTIFACTS_PRESIGN = "/v1/artifacts/presign"
65
+ ARTIFACTS_FINALIZE = "/v1/artifacts/finalize"
66
+
67
+ @staticmethod
68
+ def artifact(artifact_id: str) -> str:
69
+ """Get path for a specific artifact."""
70
+ return f"/v1/artifacts/{artifact_id}"
71
+
72
+ # API Keys endpoints
73
+ KEYS = "/v1/keys"
74
+
75
+ # History endpoints (alias for mindspace messages)
76
+ HISTORY_MESSAGES = "/v1/mindspaces/messages"