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.
- magick_mind/__init__.py +39 -0
- magick_mind/auth/__init__.py +9 -0
- magick_mind/auth/base.py +46 -0
- magick_mind/auth/email_password.py +268 -0
- magick_mind/client.py +188 -0
- magick_mind/config.py +28 -0
- magick_mind/exceptions.py +107 -0
- magick_mind/http/__init__.py +5 -0
- magick_mind/http/client.py +313 -0
- magick_mind/models/__init__.py +17 -0
- magick_mind/models/auth.py +30 -0
- magick_mind/models/common.py +32 -0
- magick_mind/models/errors.py +73 -0
- magick_mind/models/v1/__init__.py +83 -0
- magick_mind/models/v1/api_keys.py +115 -0
- magick_mind/models/v1/artifact.py +151 -0
- magick_mind/models/v1/chat.py +104 -0
- magick_mind/models/v1/corpus.py +82 -0
- magick_mind/models/v1/end_user.py +75 -0
- magick_mind/models/v1/history.py +94 -0
- magick_mind/models/v1/mindspace.py +130 -0
- magick_mind/models/v1/model.py +25 -0
- magick_mind/models/v1/project.py +73 -0
- magick_mind/realtime/__init__.py +5 -0
- magick_mind/realtime/client.py +202 -0
- magick_mind/realtime/handler.py +122 -0
- magick_mind/resources/README.md +201 -0
- magick_mind/resources/__init__.py +42 -0
- magick_mind/resources/base.py +31 -0
- magick_mind/resources/v1/__init__.py +19 -0
- magick_mind/resources/v1/api_keys.py +181 -0
- magick_mind/resources/v1/artifact.py +287 -0
- magick_mind/resources/v1/chat.py +120 -0
- magick_mind/resources/v1/corpus.py +156 -0
- magick_mind/resources/v1/end_user.py +181 -0
- magick_mind/resources/v1/history.py +88 -0
- magick_mind/resources/v1/mindspace.py +331 -0
- magick_mind/resources/v1/model.py +19 -0
- magick_mind/resources/v1/project.py +155 -0
- magick_mind/routes.py +76 -0
- magickmind-0.1.1.dist-info/METADATA +593 -0
- magickmind-0.1.1.dist-info/RECORD +43 -0
- magickmind-0.1.1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""
|
|
2
|
+
API Keys resource for Magick Mind SDK v1 API.
|
|
3
|
+
|
|
4
|
+
Provides methods for managing API keys for authenticated requests.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING, Optional
|
|
10
|
+
|
|
11
|
+
from magick_mind.models.v1.api_keys import (
|
|
12
|
+
CreateApiKeyRequest,
|
|
13
|
+
CreateApiKeyResponse,
|
|
14
|
+
DeleteApiKeyResponse,
|
|
15
|
+
ListApiKeysResponse,
|
|
16
|
+
UpdateApiKeyRequest,
|
|
17
|
+
UpdateApiKeyResponse,
|
|
18
|
+
)
|
|
19
|
+
from magick_mind.routes import Routes
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
import httpx
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ApiKeysResourceV1:
|
|
26
|
+
"""Resource client for API key operations."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, http_client: httpx.Client):
|
|
29
|
+
"""
|
|
30
|
+
Initialize the API keys resource.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
http_client: Authenticated httpx client
|
|
34
|
+
"""
|
|
35
|
+
self.http = http_client
|
|
36
|
+
|
|
37
|
+
def create(
|
|
38
|
+
self,
|
|
39
|
+
user_id: str,
|
|
40
|
+
project_id: str,
|
|
41
|
+
models: list[str],
|
|
42
|
+
key_alias: str,
|
|
43
|
+
duration: Optional[str] = None,
|
|
44
|
+
team_id: Optional[str] = None,
|
|
45
|
+
max_budget: Optional[float] = None,
|
|
46
|
+
) -> CreateApiKeyResponse:
|
|
47
|
+
"""
|
|
48
|
+
Create a new API key.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
user_id: User ID that owns this key
|
|
52
|
+
project_id: Project ID to associate with
|
|
53
|
+
models: List of allowed model names
|
|
54
|
+
key_alias: Human-readable key name
|
|
55
|
+
duration: Optional validity duration (e.g., '30d', '1y')
|
|
56
|
+
team_id: Optional team ID
|
|
57
|
+
max_budget: Optional spending limit
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
CreateApiKeyResponse with the new API key
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
httpx.HTTPStatusError: If the request fails
|
|
64
|
+
|
|
65
|
+
Example:
|
|
66
|
+
>>> response = client.v1.api_keys.create(
|
|
67
|
+
... user_id="user-123",
|
|
68
|
+
... project_id="proj-456",
|
|
69
|
+
... models=["gpt-4", "gpt-3.5-turbo"],
|
|
70
|
+
... key_alias="Production Key",
|
|
71
|
+
... duration="90d",
|
|
72
|
+
... max_budget=100.0
|
|
73
|
+
... )
|
|
74
|
+
>>> print(f"API Key: {response.key.key}")
|
|
75
|
+
"""
|
|
76
|
+
payload = CreateApiKeyRequest(
|
|
77
|
+
user_id=user_id,
|
|
78
|
+
project_id=project_id,
|
|
79
|
+
models=models,
|
|
80
|
+
key_alias=key_alias,
|
|
81
|
+
duration=duration,
|
|
82
|
+
team_id=team_id,
|
|
83
|
+
max_budget=max_budget,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
resp = self.http.post(Routes.KEYS, json=payload.model_dump(exclude_none=True))
|
|
87
|
+
resp.raise_for_status()
|
|
88
|
+
|
|
89
|
+
return CreateApiKeyResponse(**resp.json())
|
|
90
|
+
|
|
91
|
+
def list(self, user_id: str) -> ListApiKeysResponse:
|
|
92
|
+
"""
|
|
93
|
+
List all API keys for a user.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
user_id: User ID to list keys for
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
ListApiKeysResponse with list of API keys
|
|
100
|
+
|
|
101
|
+
Raises:
|
|
102
|
+
httpx.HTTPStatusError: If the request fails
|
|
103
|
+
|
|
104
|
+
Example:
|
|
105
|
+
>>> response = client.v1.api_keys.list(user_id="user-123")
|
|
106
|
+
>>> for key in response.keys:
|
|
107
|
+
... print(f"{key.key_alias}: {key.key_id}")
|
|
108
|
+
"""
|
|
109
|
+
resp = self.http.get(Routes.KEYS, params={"user_id": user_id})
|
|
110
|
+
resp.raise_for_status()
|
|
111
|
+
|
|
112
|
+
return ListApiKeysResponse(**resp.json())
|
|
113
|
+
|
|
114
|
+
def update(
|
|
115
|
+
self,
|
|
116
|
+
key: str,
|
|
117
|
+
models: list[str],
|
|
118
|
+
key_alias: str,
|
|
119
|
+
duration: Optional[str] = None,
|
|
120
|
+
max_budget: Optional[float] = None,
|
|
121
|
+
) -> UpdateApiKeyResponse:
|
|
122
|
+
"""
|
|
123
|
+
Update an existing API key.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
key: The API key to update
|
|
127
|
+
models: Updated list of allowed models
|
|
128
|
+
key_alias: Updated key alias/name
|
|
129
|
+
duration: Optional updated validity duration
|
|
130
|
+
max_budget: Optional updated spending limit
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
UpdateApiKeyResponse with updated key details
|
|
134
|
+
|
|
135
|
+
Raises:
|
|
136
|
+
httpx.HTTPStatusError: If the request fails
|
|
137
|
+
|
|
138
|
+
Example:
|
|
139
|
+
>>> response = client.v1.api_keys.update(
|
|
140
|
+
... key="sk-...",
|
|
141
|
+
... models=["gpt-4", "claude-3"],
|
|
142
|
+
... key_alias="Updated Production Key",
|
|
143
|
+
... max_budget=200.0
|
|
144
|
+
... )
|
|
145
|
+
"""
|
|
146
|
+
payload = UpdateApiKeyRequest(
|
|
147
|
+
key=key,
|
|
148
|
+
models=models,
|
|
149
|
+
key_alias=key_alias,
|
|
150
|
+
duration=duration,
|
|
151
|
+
max_budget=max_budget,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
resp = self.http.put(Routes.KEYS, json=payload.model_dump(exclude_none=True))
|
|
155
|
+
resp.raise_for_status()
|
|
156
|
+
|
|
157
|
+
return UpdateApiKeyResponse(**resp.json())
|
|
158
|
+
|
|
159
|
+
def delete(self, key_id: str) -> DeleteApiKeyResponse:
|
|
160
|
+
"""
|
|
161
|
+
Delete an API key.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
key_id: The key ID to delete
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
DeleteApiKeyResponse with confirmation
|
|
168
|
+
|
|
169
|
+
Raises:
|
|
170
|
+
httpx.HTTPStatusError: If the request fails
|
|
171
|
+
|
|
172
|
+
Example:
|
|
173
|
+
>>> response = client.v1.api_keys.delete(key_id="key-abc-123")
|
|
174
|
+
>>> print(response.message)
|
|
175
|
+
"""
|
|
176
|
+
payload = {"key_id": key_id}
|
|
177
|
+
|
|
178
|
+
resp = self.http.delete(Routes.KEYS, json=payload)
|
|
179
|
+
resp.raise_for_status()
|
|
180
|
+
|
|
181
|
+
return DeleteApiKeyResponse(**resp.json())
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Artifact resource for Magick Mind SDK v1 API.
|
|
3
|
+
|
|
4
|
+
Provides methods for file upload via presigned S3 URLs and artifact management.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING, Optional
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
|
|
13
|
+
from magick_mind.models.v1.artifact import (
|
|
14
|
+
Artifact,
|
|
15
|
+
FinalizeArtifactRequest,
|
|
16
|
+
FinalizeArtifactResponse,
|
|
17
|
+
GetArtifactResponse,
|
|
18
|
+
ListArtifactsResponse,
|
|
19
|
+
PresignArtifactRequest,
|
|
20
|
+
PresignArtifactResponse,
|
|
21
|
+
)
|
|
22
|
+
from magick_mind.resources.base import BaseResource
|
|
23
|
+
from magick_mind.routes import Routes
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from httpx import Response
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ArtifactResourceV1(BaseResource):
|
|
30
|
+
"""
|
|
31
|
+
Artifact resource for managing file uploads and artifacts.
|
|
32
|
+
|
|
33
|
+
Implements presigned S3 upload flow:
|
|
34
|
+
1. Call presign_upload() to get a presigned URL
|
|
35
|
+
2. Upload file directly to S3 using the presigned URL
|
|
36
|
+
3. Webhook or finalize() confirms completion
|
|
37
|
+
4. Use get() or list() to retrieve artifact metadata
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def presign_upload(
|
|
41
|
+
self,
|
|
42
|
+
file_name: str,
|
|
43
|
+
content_type: str,
|
|
44
|
+
size_bytes: int,
|
|
45
|
+
end_user_id: Optional[str] = None,
|
|
46
|
+
corpus_id: Optional[str] = None,
|
|
47
|
+
) -> PresignArtifactResponse:
|
|
48
|
+
"""
|
|
49
|
+
Get a presigned S3 URL for uploading a file.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
file_name: Name of the file to upload (required)
|
|
53
|
+
content_type: MIME type of the file (e.g., 'application/pdf', 'image/png')
|
|
54
|
+
size_bytes: File size in bytes (must be > 0)
|
|
55
|
+
end_user_id: Optional end user identifier
|
|
56
|
+
corpus_id: Optional corpus ID to associate artifact with
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
PresignArtifactResponse containing upload_url and metadata
|
|
60
|
+
|
|
61
|
+
Example:
|
|
62
|
+
# Get presigned URL
|
|
63
|
+
response = client.v1.artifact.presign_upload(
|
|
64
|
+
file_name="document.pdf",
|
|
65
|
+
content_type="application/pdf",
|
|
66
|
+
size_bytes=1024000,
|
|
67
|
+
corpus_id="corpus-123"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Upload file to S3 using httpx
|
|
71
|
+
import httpx
|
|
72
|
+
with open("document.pdf", "rb") as f:
|
|
73
|
+
httpx.put(
|
|
74
|
+
response.upload_url,
|
|
75
|
+
content=f,
|
|
76
|
+
headers=response.required_headers
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
print(f"Artifact ID: {response.id}")
|
|
80
|
+
"""
|
|
81
|
+
request = PresignArtifactRequest(
|
|
82
|
+
file_name=file_name,
|
|
83
|
+
content_type=content_type,
|
|
84
|
+
size_bytes=size_bytes,
|
|
85
|
+
end_user_id=end_user_id,
|
|
86
|
+
corpus_id=corpus_id,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Use generic presign endpoint
|
|
90
|
+
response = self._http.post(
|
|
91
|
+
Routes.ARTIFACTS_PRESIGN, json=request.model_dump(exclude_none=True)
|
|
92
|
+
)
|
|
93
|
+
return PresignArtifactResponse(**response.json())
|
|
94
|
+
|
|
95
|
+
def upload_file(
|
|
96
|
+
self,
|
|
97
|
+
file_path: str,
|
|
98
|
+
content_type: str,
|
|
99
|
+
end_user_id: Optional[str] = None,
|
|
100
|
+
corpus_id: Optional[str] = None,
|
|
101
|
+
) -> tuple[PresignArtifactResponse, "Response"]:
|
|
102
|
+
"""
|
|
103
|
+
Convenience method to presign and upload a file in one call.
|
|
104
|
+
|
|
105
|
+
This is a high-level wrapper that:
|
|
106
|
+
1. Gets the file size
|
|
107
|
+
2. Obtains presigned URL
|
|
108
|
+
3. Uploads the file to S3
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
file_path: Path to the file to upload
|
|
112
|
+
content_type: MIME type of the file
|
|
113
|
+
end_user_id: Optional end user identifier
|
|
114
|
+
corpus_id: Optional corpus ID to associate artifact with
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Tuple of (PresignArtifactResponse, S3 upload response)
|
|
118
|
+
|
|
119
|
+
Example:
|
|
120
|
+
presign_resp, upload_resp = client.v1.artifact.upload_file(
|
|
121
|
+
file_path="./document.pdf",
|
|
122
|
+
content_type="application/pdf",
|
|
123
|
+
corpus_id="corpus-123"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
if upload_resp.status_code == 200:
|
|
127
|
+
print(f"Upload successful! Artifact ID: {presign_resp.id}")
|
|
128
|
+
"""
|
|
129
|
+
import os
|
|
130
|
+
|
|
131
|
+
# Get file size
|
|
132
|
+
size_bytes = os.path.getsize(file_path)
|
|
133
|
+
file_name = os.path.basename(file_path)
|
|
134
|
+
|
|
135
|
+
# Get presigned URL
|
|
136
|
+
presign_response = self.presign_upload(
|
|
137
|
+
file_name=file_name,
|
|
138
|
+
content_type=content_type,
|
|
139
|
+
size_bytes=size_bytes,
|
|
140
|
+
end_user_id=end_user_id,
|
|
141
|
+
corpus_id=corpus_id,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Upload to S3 using httpx
|
|
145
|
+
with open(file_path, "rb") as f:
|
|
146
|
+
upload_response = httpx.put(
|
|
147
|
+
presign_response.upload_url,
|
|
148
|
+
content=f,
|
|
149
|
+
headers=presign_response.required_headers,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
upload_response.raise_for_status()
|
|
153
|
+
return presign_response, upload_response
|
|
154
|
+
|
|
155
|
+
def get(self, artifact_id: str) -> Artifact:
|
|
156
|
+
"""
|
|
157
|
+
Get artifact metadata by ID.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
artifact_id: The artifact ID to retrieve
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Artifact object with metadata
|
|
164
|
+
|
|
165
|
+
Example:
|
|
166
|
+
artifact = client.v1.artifact.get(artifact_id="art-123")
|
|
167
|
+
print(f"Status: {artifact.status}")
|
|
168
|
+
print(f"S3 URL: {artifact.s3_url}")
|
|
169
|
+
"""
|
|
170
|
+
response = self._http.get(Routes.artifact(artifact_id))
|
|
171
|
+
get_response = GetArtifactResponse(**response.json())
|
|
172
|
+
return get_response.artifact
|
|
173
|
+
|
|
174
|
+
def list(
|
|
175
|
+
self,
|
|
176
|
+
corpus_id: Optional[str] = None,
|
|
177
|
+
end_user_id: Optional[str] = None,
|
|
178
|
+
status: Optional[str] = None,
|
|
179
|
+
) -> list[Artifact]:
|
|
180
|
+
"""
|
|
181
|
+
List/query artifacts with optional filters.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
corpus_id: Filter by corpus ID (optional)
|
|
185
|
+
end_user_id: Filter by end user ID (optional)
|
|
186
|
+
status: Filter by status (uploaded, processing, ready, failed)
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
List of Artifact objects
|
|
190
|
+
|
|
191
|
+
Example:
|
|
192
|
+
# Get all artifacts for a corpus
|
|
193
|
+
artifacts = client.v1.artifact.list(corpus_id="corpus-123")
|
|
194
|
+
for artifact in artifacts:
|
|
195
|
+
print(f"- {artifact.id}: {artifact.status}")
|
|
196
|
+
|
|
197
|
+
# Get ready artifacts
|
|
198
|
+
ready = client.v1.artifact.list(status="ready")
|
|
199
|
+
"""
|
|
200
|
+
params = {}
|
|
201
|
+
if corpus_id is not None:
|
|
202
|
+
params["corpus_id"] = corpus_id
|
|
203
|
+
if end_user_id is not None:
|
|
204
|
+
params["end_user_id"] = end_user_id
|
|
205
|
+
if status is not None:
|
|
206
|
+
params["status"] = status
|
|
207
|
+
|
|
208
|
+
response = self._http.get(Routes.ARTIFACTS, params=params)
|
|
209
|
+
list_response = ListArtifactsResponse(**response.json())
|
|
210
|
+
return list_response.artifacts
|
|
211
|
+
|
|
212
|
+
def delete(self, artifact_id: str) -> None:
|
|
213
|
+
"""
|
|
214
|
+
Delete an artifact.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
artifact_id: The artifact ID to delete
|
|
218
|
+
|
|
219
|
+
Example:
|
|
220
|
+
client.v1.artifact.delete(artifact_id="art-123")
|
|
221
|
+
print("Artifact deleted successfully")
|
|
222
|
+
"""
|
|
223
|
+
self._http.delete(Routes.artifact(artifact_id))
|
|
224
|
+
|
|
225
|
+
def finalize(
|
|
226
|
+
self,
|
|
227
|
+
artifact_id: str,
|
|
228
|
+
bucket: str,
|
|
229
|
+
key: str,
|
|
230
|
+
version_id: Optional[str] = None,
|
|
231
|
+
size_bytes: Optional[int] = None,
|
|
232
|
+
content_type: Optional[str] = None,
|
|
233
|
+
etag: Optional[str] = None,
|
|
234
|
+
checksum_sha256: Optional[str] = None,
|
|
235
|
+
corpus_id: Optional[str] = None,
|
|
236
|
+
) -> FinalizeArtifactResponse:
|
|
237
|
+
"""
|
|
238
|
+
Client-driven finalize (fallback when webhook is unavailable).
|
|
239
|
+
|
|
240
|
+
This is typically used in local development or when the S3 Lambda
|
|
241
|
+
webhook path is not available. In production, webhooks handle
|
|
242
|
+
finalization automatically.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
artifact_id: Artifact ID from presign response
|
|
246
|
+
bucket: S3 bucket name
|
|
247
|
+
key: S3 object key
|
|
248
|
+
version_id: S3 version ID (optional)
|
|
249
|
+
size_bytes: Actual uploaded size (optional)
|
|
250
|
+
content_type: Content type (optional)
|
|
251
|
+
etag: S3 ETag (optional)
|
|
252
|
+
checksum_sha256: SHA256 checksum (optional)
|
|
253
|
+
corpus_id: Corpus ID to finalize under (optional)
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
FinalizeArtifactResponse
|
|
257
|
+
|
|
258
|
+
Example:
|
|
259
|
+
# After uploading to S3
|
|
260
|
+
response = client.v1.artifact.finalize(
|
|
261
|
+
artifact_id=presign_resp.id,
|
|
262
|
+
bucket=presign_resp.bucket,
|
|
263
|
+
key=presign_resp.key,
|
|
264
|
+
corpus_id="corpus-123"
|
|
265
|
+
)
|
|
266
|
+
print(f"Finalize status: {response.success}")
|
|
267
|
+
"""
|
|
268
|
+
request = FinalizeArtifactRequest(
|
|
269
|
+
artifact_id=artifact_id,
|
|
270
|
+
bucket=bucket,
|
|
271
|
+
key=key,
|
|
272
|
+
version_id=version_id,
|
|
273
|
+
size_bytes=size_bytes,
|
|
274
|
+
content_type=content_type,
|
|
275
|
+
etag=etag,
|
|
276
|
+
checksum_sha256=checksum_sha256,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
# Route to corpus-scoped finalize if corpus_id provided
|
|
280
|
+
if corpus_id:
|
|
281
|
+
endpoint = Routes.corpus_artifacts_finalize(corpus_id)
|
|
282
|
+
else:
|
|
283
|
+
# Generic finalize endpoint (if available)
|
|
284
|
+
endpoint = Routes.ARTIFACTS_FINALIZE
|
|
285
|
+
|
|
286
|
+
response = self._http.post(endpoint, json=request.model_dump(exclude_none=True))
|
|
287
|
+
return FinalizeArtifactResponse(**response.json())
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""V1 chat resource implementation."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Optional
|
|
4
|
+
|
|
5
|
+
from magick_mind.models.v1.chat import (
|
|
6
|
+
ChatSendRequest,
|
|
7
|
+
ChatSendResponse,
|
|
8
|
+
ConfigSchema,
|
|
9
|
+
)
|
|
10
|
+
from magick_mind.resources.base import BaseResource
|
|
11
|
+
from magick_mind.routes import Routes
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from magick_mind.http import HTTPClient
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ChatResourceV1(BaseResource):
|
|
18
|
+
"""
|
|
19
|
+
Chat resource client for V1 API.
|
|
20
|
+
|
|
21
|
+
Provides typed interface for sending chat messages to mindspaces.
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
response = client.v1.chat.send(
|
|
25
|
+
api_key="sk-...",
|
|
26
|
+
mindspace_id="mind-123",
|
|
27
|
+
message="Hello!",
|
|
28
|
+
enduser_id="user-456",
|
|
29
|
+
config=ConfigSchema(
|
|
30
|
+
fast_model_id="gpt-4",
|
|
31
|
+
smart_model_ids=["gpt-4"],
|
|
32
|
+
compute_power=50,
|
|
33
|
+
),
|
|
34
|
+
)
|
|
35
|
+
print(response.content.content) # AI response text
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def send(
|
|
39
|
+
self,
|
|
40
|
+
api_key: str,
|
|
41
|
+
mindspace_id: str,
|
|
42
|
+
message: str,
|
|
43
|
+
enduser_id: str,
|
|
44
|
+
config: ConfigSchema,
|
|
45
|
+
reply_to_message_id: Optional[str] = None,
|
|
46
|
+
additional_context: Optional[str] = None,
|
|
47
|
+
artifact_ids: Optional[list[str]] = None,
|
|
48
|
+
) -> ChatSendResponse:
|
|
49
|
+
"""
|
|
50
|
+
Send a chat message to a mindspace and get AI response.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
api_key: API key for LLM access
|
|
54
|
+
mindspace_id: Mindspace/chat conversation ID
|
|
55
|
+
message: User message text to send
|
|
56
|
+
enduser_id: End-user identifier
|
|
57
|
+
config: Model configuration (fast_model_id, smart_model_ids, compute_power)
|
|
58
|
+
reply_to_message_id: Optional ID of message being replied to
|
|
59
|
+
additional_context: Optional additional context for the message
|
|
60
|
+
artifact_ids: Optional list of artifact IDs to attach to message
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
ChatSendResponse with AI-generated response
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
ValidationError: If message is empty, mindspace_id invalid, or required fields missing
|
|
67
|
+
ProblemDetailsException: If mindspace not found (404), permission denied (403),
|
|
68
|
+
or server error (500+). Always includes request_id for support.
|
|
69
|
+
RateLimitError: If API rate limit exceeded (429)
|
|
70
|
+
AuthenticationError: If JWT token is invalid or expired (auto-refreshed transparently)
|
|
71
|
+
|
|
72
|
+
Example:
|
|
73
|
+
# Basic chat with config
|
|
74
|
+
response = chat.send(
|
|
75
|
+
api_key="sk-test",
|
|
76
|
+
mindspace_id="mind-123",
|
|
77
|
+
message="What's the weather?",
|
|
78
|
+
enduser_id="user-456",
|
|
79
|
+
config=ConfigSchema(
|
|
80
|
+
fast_model_id="gpt-4",
|
|
81
|
+
smart_model_ids=["gpt-4"],
|
|
82
|
+
),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Chat with attached artifacts
|
|
86
|
+
response = chat.send(
|
|
87
|
+
api_key="sk-test",
|
|
88
|
+
mindspace_id="mind-123",
|
|
89
|
+
message="Analyze these documents",
|
|
90
|
+
enduser_id="user-456",
|
|
91
|
+
config=ConfigSchema(
|
|
92
|
+
fast_model_id="gpt-4",
|
|
93
|
+
smart_model_ids=["gpt-4"],
|
|
94
|
+
compute_power=80,
|
|
95
|
+
),
|
|
96
|
+
artifact_ids=["art-123", "art-456"],
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
print(f"AI: {response.content.content}")
|
|
100
|
+
print(f"Message ID: {response.content.message_id}")
|
|
101
|
+
"""
|
|
102
|
+
# Build and validate request
|
|
103
|
+
request = ChatSendRequest(
|
|
104
|
+
api_key=api_key,
|
|
105
|
+
mindspace_id=mindspace_id,
|
|
106
|
+
message=message,
|
|
107
|
+
enduser_id=enduser_id,
|
|
108
|
+
config=config,
|
|
109
|
+
reply_to_message_id=reply_to_message_id,
|
|
110
|
+
additional_context=additional_context,
|
|
111
|
+
artifact_ids=artifact_ids,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Make API call
|
|
115
|
+
response = self._http.post(
|
|
116
|
+
Routes.CHAT, json=request.model_dump(exclude_none=True)
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Parse and validate response
|
|
120
|
+
return ChatSendResponse.model_validate(response)
|