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,313 @@
1
+ """HTTP client for making API requests."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import http.client
6
+ import json
7
+ import logging
8
+ from typing import Any, Dict, Optional
9
+
10
+ import httpx
11
+ from pydantic import ValidationError as PydanticValidationError
12
+
13
+ from magick_mind.auth.base import AuthProvider
14
+ from magick_mind.config import SDKConfig
15
+ from magick_mind.exceptions import (
16
+ MagickMindError,
17
+ ProblemDetailsException,
18
+ RateLimitError,
19
+ ValidationError,
20
+ )
21
+ from magick_mind.models.errors import ErrorResponse, ProblemDetails
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ class HTTPClient:
27
+ """
28
+ HTTP client wrapper for making authenticated API requests.
29
+
30
+ Handles:
31
+ - Authentication header injection
32
+ - Error handling and response parsing
33
+ - Retry logic
34
+ - Request/response logging
35
+ """
36
+
37
+ def __init__(self, config: SDKConfig, auth: AuthProvider):
38
+ """
39
+ Initialize HTTP client.
40
+
41
+ Args:
42
+ config: SDK configuration
43
+ auth: Authentication provider
44
+ """
45
+ self.config = config
46
+ self.auth = auth
47
+ self._client = httpx.Client(
48
+ timeout=config.timeout,
49
+ verify=config.verify_ssl,
50
+ )
51
+
52
+ def _get_headers(
53
+ self, extra_headers: Optional[Dict[str, str]] = None
54
+ ) -> Dict[str, str]:
55
+ """Build request headers with authentication."""
56
+ headers = {
57
+ "Content-Type": "application/json",
58
+ "Accept": "application/json",
59
+ }
60
+
61
+ # Add auth headers
62
+ auth_headers = self.auth.get_headers()
63
+ headers.update(auth_headers)
64
+
65
+ # Add any extra headers
66
+ if extra_headers:
67
+ headers.update(extra_headers)
68
+
69
+ return headers
70
+
71
+ def _build_url(self, path: str) -> str:
72
+ """Build full URL from path."""
73
+ base = self.config.normalized_base_url()
74
+ # Ensure path starts with /
75
+ if not path.startswith("/"):
76
+ path = f"/{path}"
77
+ return f"{base}{path}"
78
+
79
+ def _handle_response(self, response: httpx.Response) -> Dict[str, Any]:
80
+ """
81
+ Handle HTTP response and raise appropriate exceptions.
82
+
83
+ Args:
84
+ response: HTTP response object
85
+
86
+ Returns:
87
+ Parsed JSON response data
88
+
89
+ Raises:
90
+ ProblemDetailsException: For RFC 7807 errors
91
+ ValidationError: For 400 Bad Request with field errors
92
+ RateLimitError: For rate limiting
93
+ MagickMindError: For malformed responses
94
+ """
95
+ # Success path
96
+ if response.status_code < 400:
97
+ try:
98
+ return response.json()
99
+ except Exception:
100
+ return {}
101
+
102
+ # Rate limiting (special case)
103
+ if response.status_code == 429:
104
+ # Try RFC 7807 first
105
+ try:
106
+ error_response = ErrorResponse.model_validate(response.json())
107
+ raise RateLimitError(
108
+ error_response.error.detail,
109
+ status_code=429,
110
+ )
111
+ except (json.JSONDecodeError, PydanticValidationError):
112
+ raise RateLimitError(
113
+ "Rate limit exceeded",
114
+ status_code=429,
115
+ )
116
+
117
+ # Parse error response
118
+ try:
119
+ data = response.json()
120
+ except json.JSONDecodeError:
121
+ raise MagickMindError(
122
+ f"Non-JSON error response: {response.text[:200]}",
123
+ status_code=response.status_code,
124
+ )
125
+
126
+ # RFC 7807 format (98% of Bifrost endpoints)
127
+ if "error" in data and isinstance(data["error"], dict):
128
+ try:
129
+ error_response = ErrorResponse.model_validate(data)
130
+ problem = error_response.error
131
+
132
+ # Raise ValidationError for 400 with field errors
133
+ if problem.status == 400 and problem.errors:
134
+ raise ValidationError(problem, raw_response=data)
135
+
136
+ # Generic ProblemDetailsException
137
+ raise ProblemDetailsException(problem, raw_response=data)
138
+
139
+ except PydanticValidationError as e:
140
+ # Malformed RFC 7807 response
141
+ logger.warning("Malformed RFC 7807 response: %s", e)
142
+ raise MagickMindError(
143
+ f"Malformed error response: {data.get('error', {}).get('detail', 'Unknown error')}",
144
+ status_code=response.status_code,
145
+ )
146
+
147
+ # Fallback: OpenAI middleware format {"code": 401, "message": "..."}
148
+ if "code" in data and "message" in data:
149
+ logger.debug("Received legacy error format from OpenAI middleware")
150
+ # Convert to RFC 7807 structure
151
+ problem = ProblemDetails(
152
+ type="about:blank",
153
+ title=http.client.responses.get(data["code"], "Error"),
154
+ status=data["code"],
155
+ detail=data["message"],
156
+ )
157
+ raise ProblemDetailsException(problem, raw_response=data)
158
+
159
+ # Unknown format
160
+ raise MagickMindError(
161
+ f"Unknown error response format: {data}",
162
+ status_code=response.status_code,
163
+ )
164
+
165
+ def get(
166
+ self,
167
+ path: str,
168
+ params: Optional[Dict[str, Any]] = None,
169
+ headers: Optional[Dict[str, str]] = None,
170
+ ) -> Dict[str, Any]:
171
+ """
172
+ Make a GET request.
173
+
174
+ Args:
175
+ path: API endpoint path
176
+ params: Query parameters
177
+ headers: Additional headers
178
+
179
+ Returns:
180
+ Response data as dictionary
181
+
182
+ Raises:
183
+ AuthenticationError: If JWT token is invalid (auto-refreshed if expired)
184
+ ProblemDetailsException: For API errors (4xx, 5xx) following RFC 7807
185
+ ValidationError: For 400 Bad Request with field-level errors
186
+ RateLimitError: For 429 Too Many Requests
187
+ MagickMindError: For unexpected errors or malformed responses
188
+
189
+ Example:
190
+ >>> response = client.http.get("/v1/mindspaces")
191
+ >>> print(response['data'])
192
+ """
193
+ # Refresh auth if needed
194
+ self.auth.refresh_if_needed()
195
+
196
+ url = self._build_url(path)
197
+ request_headers = self._get_headers(headers)
198
+
199
+ response = self._client.get(url, params=params, headers=request_headers)
200
+ return self._handle_response(response)
201
+
202
+ def post(
203
+ self,
204
+ path: str,
205
+ json: Optional[Dict[str, Any]] = None,
206
+ headers: Optional[Dict[str, str]] = None,
207
+ ) -> Dict[str, Any]:
208
+ """
209
+ Make a POST request.
210
+
211
+ Args:
212
+ path: API endpoint path
213
+ json: Request body as dictionary
214
+ headers: Additional headers
215
+
216
+ Returns:
217
+ Response data as dictionary
218
+
219
+ Raises:
220
+ AuthenticationError: If JWT token is invalid (auto-refreshed if expired)
221
+ ProblemDetailsException: For API errors (4xx, 5xx) following RFC 7807
222
+ ValidationError: For 400 Bad Request with field-level validation errors
223
+ RateLimitError: For 429 Too Many Requests
224
+ MagickMindError: For unexpected errors or malformed responses
225
+
226
+ Example:
227
+ >>> response = client.http.post(
228
+ ... "/v1/magickmind/chat",
229
+ ... json={"message": "Hello", "mindspace_id": "mind-123"}
230
+ ... )
231
+ """
232
+ # Refresh auth if needed
233
+ self.auth.refresh_if_needed()
234
+
235
+ url = self._build_url(path)
236
+ request_headers = self._get_headers(headers)
237
+
238
+ response = self._client.post(url, json=json, headers=request_headers)
239
+ return self._handle_response(response)
240
+
241
+ def put(
242
+ self,
243
+ path: str,
244
+ json: Optional[Dict[str, Any]] = None,
245
+ headers: Optional[Dict[str, str]] = None,
246
+ ) -> Dict[str, Any]:
247
+ """
248
+ Make a PUT request.
249
+
250
+ Args:
251
+ path: API endpoint path
252
+ json: Request body as dictionary
253
+ headers: Additional headers
254
+
255
+ Returns:
256
+ Response data as dictionary
257
+
258
+ Raises:
259
+ AuthenticationError: If JWT token is invalid (auto-refreshed if expired)
260
+ ProblemDetailsException: For API errors (4xx, 5xx) following RFC 7807
261
+ ValidationError: For 400 Bad Request with field-level validation errors
262
+ RateLimitError: For 429 Too Many Requests
263
+ MagickMindError: For unexpected errors or malformed responses
264
+ """
265
+ # Refresh auth if needed
266
+ self.auth.refresh_if_needed()
267
+
268
+ url = self._build_url(path)
269
+ request_headers = self._get_headers(headers)
270
+
271
+ response = self._client.put(url, json=json, headers=request_headers)
272
+ return self._handle_response(response)
273
+
274
+ def delete(
275
+ self, path: str, headers: Optional[Dict[str, str]] = None
276
+ ) -> Dict[str, Any]:
277
+ """
278
+ Make a DELETE request.
279
+
280
+ Args:
281
+ path: API endpoint path
282
+ headers: Additional headers
283
+
284
+ Returns:
285
+ Response data as dictionary
286
+
287
+ Raises:
288
+ AuthenticationError: If JWT token is invalid (auto-refreshed if expired)
289
+ ProblemDetailsException: For API errors (4xx, 5xx) following RFC 7807
290
+ ValidationError: For 400 Bad Request with field-level validation errors
291
+ RateLimitError: For 429 Too Many Requests
292
+ MagickMindError: For unexpected errors or malformed responses
293
+ """
294
+ # Refresh auth if needed
295
+ self.auth.refresh_if_needed()
296
+
297
+ url = self._build_url(path)
298
+ request_headers = self._get_headers(headers)
299
+
300
+ response = self._client.delete(url, headers=request_headers)
301
+ return self._handle_response(response)
302
+
303
+ def close(self) -> None:
304
+ """Close the HTTP client."""
305
+ self._client.close()
306
+
307
+ def __enter__(self):
308
+ """Context manager entry."""
309
+ return self
310
+
311
+ def __exit__(self, exc_type, exc_val, exc_tb):
312
+ """Context manager exit."""
313
+ self.close()
@@ -0,0 +1,17 @@
1
+ """Pydantic models for API requests and responses."""
2
+
3
+ # Models are organized by version
4
+ # Each version has its own submodule with request/response schemas
5
+
6
+ # RFC 7807 error models (version-agnostic)
7
+ from magick_mind.models.errors import (
8
+ ErrorResponse,
9
+ ProblemDetails,
10
+ ValidationErrorField,
11
+ )
12
+
13
+ __all__ = [
14
+ "ErrorResponse",
15
+ "ProblemDetails",
16
+ "ValidationErrorField",
17
+ ]
@@ -0,0 +1,30 @@
1
+ """Authentication-related Pydantic models."""
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class LoginRequest(BaseModel):
7
+ """Request model for /v1/auth/login."""
8
+
9
+ email: str
10
+ password: str
11
+
12
+
13
+ class RefreshRequest(BaseModel):
14
+ """Request model for /v1/auth/refresh."""
15
+
16
+ refresh_token: str
17
+
18
+
19
+ class TokenResponse(BaseModel):
20
+ """Response model from /v1/auth/login."""
21
+
22
+ access_token: str
23
+ expires_in: int
24
+ refresh_expires_in: int
25
+ refresh_token: str
26
+ token_type: str
27
+ id_token: str
28
+ not_before_policy: int = Field(..., alias="not-before-policy")
29
+ session_state: str
30
+ scope: str
@@ -0,0 +1,32 @@
1
+ """Common Pydantic models used across versions."""
2
+
3
+ from typing import Optional
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class BaseResponse(BaseModel):
9
+ """Base response structure from Bifrost API."""
10
+
11
+ success: bool
12
+ message: str
13
+
14
+
15
+ class Cursors(BaseModel):
16
+ """Pagination cursors for Bifrost paginated responses."""
17
+
18
+ after: Optional[str] = Field(default=None, description="Cursor for next page")
19
+ before: Optional[str] = Field(default=None, description="Cursor for previous page")
20
+
21
+
22
+ class PageInfo(BaseModel):
23
+ """Pagination information for Bifrost paginated responses."""
24
+
25
+ cursors: Cursors = Field(
26
+ default_factory=lambda: Cursors(after=None, before=None),
27
+ description="Pagination cursors",
28
+ )
29
+ has_more: bool = Field(default=False, description="Whether there are more results")
30
+ has_previous: bool = Field(
31
+ default=False, description="Whether there are previous results"
32
+ )
@@ -0,0 +1,73 @@
1
+ """RFC 7807 Problem Details error models for Bifrost API responses.
2
+
3
+ This module defines Pydantic models for parsing RFC 7807 compliant error
4
+ responses from the Bifrost API.
5
+
6
+ See: https://datatracker.ietf.org/doc/html/rfc7807
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from pydantic import BaseModel, ConfigDict, Field
12
+
13
+
14
+ class ValidationErrorField(BaseModel):
15
+ """RFC 7807 field-level validation error.
16
+
17
+ Represents a single field validation error, typically used in 400 Bad Request
18
+ responses with field-specific validation failures.
19
+
20
+ Attributes:
21
+ field: The field name that failed validation
22
+ message: Human-readable error message for this field
23
+ code: Optional error code (e.g., "required", "invalid_format")
24
+ """
25
+
26
+ field: str
27
+ message: str
28
+ code: str | None = None
29
+
30
+
31
+ class ProblemDetails(BaseModel):
32
+ """RFC 7807 Problem Details structure.
33
+
34
+ Standard structure for HTTP API error responses. This is the Pydantic model
35
+ representing the data structure, NOT the exception class.
36
+
37
+ The exception class that wraps this is ProblemDetailsException (created in task 02).
38
+
39
+ Attributes:
40
+ type: URI reference identifying the problem type (default: "about:blank")
41
+ title: Short, human-readable summary of the problem type
42
+ status: HTTP status code (400-599)
43
+ detail: Human-readable explanation specific to this occurrence
44
+ instance: URI reference identifying the specific occurrence (e.g., request path)
45
+ request_id: Trace ID for debugging (Bifrost extension field)
46
+ errors: List of field-level validation errors (Bifrost extension field)
47
+
48
+ Additional fields are allowed per RFC 7807 extension mechanism.
49
+ """
50
+
51
+ type: str = "about:blank"
52
+ title: str
53
+ status: int = Field(ge=400, le=599)
54
+ detail: str
55
+ instance: str | None = None
56
+ request_id: str | None = None
57
+ errors: list[ValidationErrorField] = Field(default_factory=list)
58
+
59
+ model_config = ConfigDict(extra="allow")
60
+
61
+
62
+ class ErrorResponse(BaseModel):
63
+ """Bifrost error response wrapper.
64
+
65
+ Wraps the RFC 7807 ProblemDetails in an "error" envelope as returned by Bifrost.
66
+
67
+ Bifrost returns: {"error": {...problem details...}}
68
+
69
+ Attributes:
70
+ error: The RFC 7807 problem details
71
+ """
72
+
73
+ error: ProblemDetails
@@ -0,0 +1,83 @@
1
+ """V1 API models."""
2
+
3
+ from magick_mind.models.v1.artifact import (
4
+ Artifact,
5
+ ArtifactWebhookPayload,
6
+ DeleteArtifactResponse,
7
+ FinalizeArtifactRequest,
8
+ FinalizeArtifactResponse,
9
+ GetArtifactResponse,
10
+ ListArtifactsResponse,
11
+ PresignArtifactRequest,
12
+ PresignArtifactResponse,
13
+ )
14
+ from magick_mind.models.v1.chat import ChatPayload, ChatSendRequest, ChatSendResponse
15
+ from magick_mind.models.v1.corpus import (
16
+ Corpus,
17
+ CreateCorpusRequest,
18
+ ListCorpusResponse,
19
+ UpdateCorpusRequest,
20
+ )
21
+ from magick_mind.models.v1.end_user import (
22
+ CreateEndUserRequest,
23
+ EndUser,
24
+ QueryEndUserResponse,
25
+ UpdateEndUserRequest,
26
+ )
27
+ from magick_mind.models.v1.history import ChatHistoryMessage, HistoryResponse
28
+ from magick_mind.models.v1.api_keys import (
29
+ ApiKey,
30
+ CreateApiKeyRequest,
31
+ CreateApiKeyResponse,
32
+ DeleteApiKeyResponse,
33
+ KeyResponse,
34
+ ListApiKeysResponse,
35
+ UpdateApiKeyRequest,
36
+ UpdateApiKeyResponse,
37
+ )
38
+ from magick_mind.models.v1.project import (
39
+ CreateProjectRequest,
40
+ GetProjectListResponse,
41
+ Project,
42
+ UpdateProjectRequest,
43
+ )
44
+ from magick_mind.models.v1.model import Model, ModelsListResponse
45
+
46
+
47
+ __all__ = [
48
+ "ChatSendRequest",
49
+ "ChatPayload",
50
+ "ChatSendResponse",
51
+ "ChatHistoryMessage",
52
+ "HistoryResponse",
53
+ "Project",
54
+ "CreateProjectRequest",
55
+ "GetProjectListResponse",
56
+ "EndUser",
57
+ "CreateEndUserRequest",
58
+ "QueryEndUserResponse",
59
+ "UpdateEndUserRequest",
60
+ "Artifact",
61
+ "PresignArtifactRequest",
62
+ "PresignArtifactResponse",
63
+ "GetArtifactResponse",
64
+ "ListArtifactsResponse",
65
+ "DeleteArtifactResponse",
66
+ "FinalizeArtifactRequest",
67
+ "FinalizeArtifactResponse",
68
+ "ArtifactWebhookPayload",
69
+ "Corpus",
70
+ "CreateCorpusRequest",
71
+ "ListCorpusResponse",
72
+ "UpdateCorpusRequest",
73
+ "ApiKey",
74
+ "KeyResponse",
75
+ "CreateApiKeyRequest",
76
+ "CreateApiKeyResponse",
77
+ "ListApiKeysResponse",
78
+ "UpdateApiKeyRequest",
79
+ "UpdateApiKeyResponse",
80
+ "DeleteApiKeyResponse",
81
+ "Model",
82
+ "ModelsListResponse",
83
+ ]
@@ -0,0 +1,115 @@
1
+ """
2
+ API Keys models for Magick Mind SDK v1 API.
3
+
4
+ Provides Pydantic models for creating and managing API keys
5
+ for authenticating requests to the Bifrost backend.
6
+ """
7
+
8
+ from typing import Optional
9
+
10
+ from pydantic import BaseModel, ConfigDict, Field
11
+
12
+
13
+ class ApiKey(BaseModel):
14
+ """
15
+ API key metadata (does not include the actual key value).
16
+
17
+ Represents an API key for making authenticated requests.
18
+ """
19
+
20
+ model_config = ConfigDict(extra="allow")
21
+
22
+ key_id: str = Field(..., description="Unique key identifier")
23
+ key_alias: str = Field(..., description="Human-readable key alias/name")
24
+ user_id: str = Field(..., description="Owner user ID")
25
+ project_id: str = Field(..., description="Associated project ID")
26
+ update_at: str = Field(..., description="Last update timestamp")
27
+ create_at: str = Field(..., description="Creation timestamp")
28
+
29
+
30
+ class KeyResponse(BaseModel):
31
+ """
32
+ Response containing the actual API key value.
33
+
34
+ Only returned when creating or updating a key.
35
+ """
36
+
37
+ model_config = ConfigDict(extra="allow")
38
+
39
+ key: Optional[str] = Field(None, description="The actual API key (only shown once)")
40
+ key_alias: Optional[str] = Field(None, description="Key alias/name")
41
+ key_id: Optional[str] = Field(None, description="Key identifier")
42
+ expires: Optional[str] = Field(None, description="Expiration timestamp")
43
+
44
+
45
+ class CreateApiKeyRequest(BaseModel):
46
+ """Request for creating a new API key."""
47
+
48
+ user_id: Optional[str] = Field(
49
+ None, description="User ID that owns this key (Relaxed)"
50
+ )
51
+ project_id: Optional[str] = Field(
52
+ None, description="Project ID to associate with (Relaxed)"
53
+ )
54
+ models: Optional[list[str]] = Field(None, description="Allowed models (Relaxed)")
55
+ key_alias: Optional[str] = Field(
56
+ None, description="Human-readable key name (Relaxed)"
57
+ )
58
+ duration: Optional[str] = Field(
59
+ None, description="Key validity duration (e.g., '30d', '1y')"
60
+ )
61
+ team_id: Optional[str] = Field(None, description="Optional team ID")
62
+ max_budget: Optional[float] = Field(None, description="Optional spending limit")
63
+
64
+
65
+ class CreateApiKeyResponse(BaseModel):
66
+ """Response for API key creation."""
67
+
68
+ success: bool = Field(..., description="Request success status")
69
+ message: str = Field(..., description="Response message")
70
+ key: KeyResponse = Field(..., description="The created API key details")
71
+
72
+
73
+ class ListApiKeysResponse(BaseModel):
74
+ """Response for listing API keys."""
75
+
76
+ success: bool = Field(..., description="Request success status")
77
+ message: str = Field(..., description="Response message")
78
+ keys: list[ApiKey] = Field(..., description="List of API keys (metadata only)")
79
+
80
+
81
+ class UpdateApiKeyRequest(BaseModel):
82
+ """Request for updating an existing API key."""
83
+
84
+ key: Optional[str] = Field(None, description="The API key to update (Relaxed)")
85
+ models: Optional[list[str]] = Field(
86
+ None, description="Updated allowed models (Relaxed)"
87
+ )
88
+ key_alias: Optional[str] = Field(
89
+ None, description="Updated key alias/name (Relaxed)"
90
+ )
91
+ duration: Optional[str] = Field(None, description="Updated validity duration")
92
+ max_budget: Optional[float] = Field(None, description="Updated spending limit")
93
+
94
+
95
+ class UpdateApiKeyResponse(BaseModel):
96
+ """Response for API key update."""
97
+
98
+ success: Optional[bool] = Field(None, description="Request success status")
99
+ message: Optional[str] = Field(None, description="Response message")
100
+ key: Optional[KeyResponse] = Field(
101
+ None, description="The updated API key details (Optional in Apidog)"
102
+ )
103
+
104
+
105
+ class DeleteApiKeyRequest(BaseModel):
106
+ """Request for deleting an API key."""
107
+
108
+ key_id: str = Field(..., description="Key ID to delete")
109
+
110
+
111
+ class DeleteApiKeyResponse(BaseModel):
112
+ """Response for API key deletion."""
113
+
114
+ success: bool = Field(..., description="Request success status")
115
+ message: str = Field(..., description="Deletion confirmation message")