codemie-sdk-python 0.1.226__py3-none-any.whl → 0.1.256__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.
codemie_sdk/__init__.py CHANGED
@@ -60,11 +60,25 @@ from .models.vendor_knowledgebase import (
60
60
  VendorKnowledgeBaseInstallResponse,
61
61
  VendorKnowledgeBaseUninstallResponse,
62
62
  )
63
+ from .models.vendor_guardrail import (
64
+ VendorGuardrailSetting,
65
+ VendorGuardrailSettingsResponse,
66
+ VendorGuardrail,
67
+ VendorGuardrailStatus,
68
+ VendorGuardrailsResponse,
69
+ VendorGuardrailVersion,
70
+ VendorGuardrailVersionsResponse,
71
+ VendorGuardrailInstallRequest,
72
+ VendorGuardrailInstallSummary,
73
+ VendorGuardrailInstallResponse,
74
+ VendorGuardrailUninstallResponse,
75
+ )
63
76
  from .services.vendor_assistant import VendorAssistantService
64
77
  from .services.vendor_workflow import VendorWorkflowService
65
78
  from .services.vendor_knowledgebase import VendorKnowledgeBaseService
79
+ from .services.vendor_guardrail import VendorGuardrailService
66
80
 
67
- __version__ = "0.2.11"
81
+ __version__ = "0.2.12"
68
82
  __all__ = [
69
83
  "CodeMieClient",
70
84
  "VendorType",
@@ -106,4 +120,16 @@ __all__ = [
106
120
  "VendorKnowledgeBaseInstallResponse",
107
121
  "VendorKnowledgeBaseUninstallResponse",
108
122
  "VendorKnowledgeBaseService",
123
+ "VendorGuardrailSetting",
124
+ "VendorGuardrailSettingsResponse",
125
+ "VendorGuardrail",
126
+ "VendorGuardrailStatus",
127
+ "VendorGuardrailsResponse",
128
+ "VendorGuardrailVersion",
129
+ "VendorGuardrailVersionsResponse",
130
+ "VendorGuardrailInstallRequest",
131
+ "VendorGuardrailInstallSummary",
132
+ "VendorGuardrailInstallResponse",
133
+ "VendorGuardrailUninstallResponse",
134
+ "VendorGuardrailService",
109
135
  ]
@@ -16,6 +16,7 @@ from ..services.webhook import WebhookService
16
16
  from ..services.vendor_assistant import VendorAssistantService
17
17
  from ..services.vendor_workflow import VendorWorkflowService
18
18
  from ..services.vendor_knowledgebase import VendorKnowledgeBaseService
19
+ from ..services.vendor_guardrail import VendorGuardrailService
19
20
 
20
21
 
21
22
  class CodeMieClient:
@@ -101,6 +102,9 @@ class CodeMieClient:
101
102
  self.vendor_knowledgebases = VendorKnowledgeBaseService(
102
103
  self._api_domain, self._token, verify_ssl=self._verify_ssl
103
104
  )
105
+ self.vendor_guardrails = VendorGuardrailService(
106
+ self._api_domain, self._token, verify_ssl=self._verify_ssl
107
+ )
104
108
 
105
109
  @property
106
110
  def token(self) -> str:
@@ -116,6 +120,7 @@ class CodeMieClient:
116
120
  "localhost",
117
121
  "127.0.0.1",
118
122
  "0.0.0.0",
123
+ "192.168",
119
124
  ]
120
125
  return any(pattern in domain_lower for pattern in localhost_patterns)
121
126
 
@@ -147,6 +152,12 @@ class CodeMieClient:
147
152
  self.conversations = ConversationService(
148
153
  self._api_domain, self._token, verify_ssl=self._verify_ssl
149
154
  )
155
+ self.files = FileOperationService(
156
+ self._api_domain, self._token, verify_ssl=self._verify_ssl
157
+ )
158
+ self.webhook = WebhookService(
159
+ self._api_domain, self._token, verify_ssl=self._verify_ssl
160
+ )
150
161
  self.vendor_assistants = VendorAssistantService(
151
162
  self._api_domain, self._token, verify_ssl=self._verify_ssl
152
163
  )
@@ -156,4 +167,7 @@ class CodeMieClient:
156
167
  self.vendor_knowledgebases = VendorKnowledgeBaseService(
157
168
  self._api_domain, self._token, verify_ssl=self._verify_ssl
158
169
  )
170
+ self.vendor_guardrails = VendorGuardrailService(
171
+ self._api_domain, self._token, verify_ssl=self._verify_ssl
172
+ )
159
173
  return self._token
@@ -53,6 +53,16 @@ class Context(BaseModel):
53
53
  name: str
54
54
 
55
55
 
56
+ class PromptVariable(BaseModel):
57
+ """Model for assistant prompt variables."""
58
+
59
+ model_config = ConfigDict(extra="ignore")
60
+
61
+ key: str
62
+ description: Optional[str] = None
63
+ default_value: str
64
+
65
+
56
66
  class MCPServerConfig(BaseModel):
57
67
  """
58
68
  Configuration for an MCP server.
@@ -137,6 +147,17 @@ class AssistantBase(BaseModel):
137
147
  icon_url: Optional[str] = None
138
148
 
139
149
 
150
+ class AssistantListResponse(BaseModel):
151
+ """Model for assistant list response."""
152
+
153
+ model_config = ConfigDict(extra="ignore")
154
+
155
+ id: str
156
+ name: str
157
+ slug: Optional[str] = None
158
+ created_by: Optional[User] = None
159
+
160
+
140
161
  class Assistant(AssistantBase):
141
162
  """Full assistant model with additional fields."""
142
163
 
@@ -164,6 +185,8 @@ class Assistant(AssistantBase):
164
185
  user_abilities: Optional[List[Any]] = None
165
186
  mcp_servers: List[MCPServerDetails] = Field(default_factory=list)
166
187
  assistant_ids: List[str] = Field(default_factory=list)
188
+ version_count: Optional[int] = None
189
+ prompt_variables: Optional[List[PromptVariable]] = Field(default=None)
167
190
 
168
191
 
169
192
  class AssistantRequestBase(AssistantBase):
@@ -191,6 +214,7 @@ class AssistantRequestBase(AssistantBase):
191
214
  top_p: Optional[float] = None
192
215
  mcp_servers: List[MCPServerDetails] = Field(default_factory=list)
193
216
  assistant_ids: List[str] = Field(default_factory=list)
217
+ prompt_variables: List[PromptVariable] = Field(default_factory=list)
194
218
 
195
219
 
196
220
  class AssistantCreateRequest(AssistantRequestBase):
@@ -205,6 +229,27 @@ class AssistantUpdateRequest(AssistantRequestBase):
205
229
  pass
206
230
 
207
231
 
232
+ class AssistantVersion(BaseModel):
233
+ """Immutable snapshot of assistant configuration for a specific version."""
234
+
235
+ model_config = ConfigDict(extra="ignore", use_enum_values=True)
236
+
237
+ version_number: int
238
+ created_date: datetime
239
+ created_by: Optional[User] = None
240
+ change_notes: Optional[str] = None
241
+ description: Optional[str] = None
242
+ system_prompt: str
243
+ llm_model_type: Optional[str] = None
244
+ temperature: Optional[float] = None
245
+ top_p: Optional[float] = None
246
+ context: List[Context] = Field(default_factory=list)
247
+ toolkits: List[ToolKitDetails] = Field(default_factory=list)
248
+ mcp_servers: List[MCPServerDetails] = Field(default_factory=list)
249
+ assistant_ids: List[str] = Field(default_factory=list)
250
+ prompt_variables: List[PromptVariable] = Field(default_factory=list)
251
+
252
+
208
253
  class ChatRole(str, Enum):
209
254
  """Enum for chat message roles."""
210
255
 
@@ -263,6 +308,14 @@ class AssistantChatRequest(BaseModel):
263
308
  default=None, description="DataSource in conversation history"
264
309
  )
265
310
  stream: bool = Field(default=False, description="Enable streaming response")
311
+ propagate_headers: bool = Field(
312
+ default=False,
313
+ description="Enable propagation of X-* HTTP headers to MCP servers during tool execution",
314
+ )
315
+ custom_metadata: Optional[dict[str, Any]] = Field(
316
+ default=None,
317
+ description="Custom metadata for the AI Assistant",
318
+ )
266
319
  top_k: int = Field(default=10, description="Top K results to consider")
267
320
  system_prompt: str = Field(default="", description="Override system prompt")
268
321
  background_task: bool = Field(default=False, description="Run as background task")
@@ -25,6 +25,8 @@ class DataSourceType(str, Enum):
25
25
  CHUNK_SUMMARY = "chunk-summary"
26
26
  JSON = "knowledge_base_json"
27
27
  BEDROCK = "knowledge_base_bedrock"
28
+ PLATFORM = "platform_marketplace_assistant"
29
+ AZURE_DEVOPS_WIKI = "knowledge_base_azure_devops_wiki"
28
30
 
29
31
 
30
32
  class DataSourceStatus(str, Enum):
@@ -46,6 +48,15 @@ class DataSourceProcessingInfo(BaseModel):
46
48
  processed_documents_count: Optional[int] = Field(None, alias="documents_count_key")
47
49
 
48
50
 
51
+ class ElasticsearchStatsResponse(BaseModel):
52
+ """Response model for Elasticsearch index statistics."""
53
+
54
+ model_config = ConfigDict(extra="ignore")
55
+
56
+ index_name: str = Field(..., description="Name of the index in Elasticsearch")
57
+ size_in_bytes: int = Field(..., ge=0, description="Size of the index in bytes")
58
+
59
+
49
60
  # Base request models
50
61
  class Confluence(BaseModel):
51
62
  """Model for Confluence-specific response fields"""
@@ -259,6 +270,40 @@ class UpdateFileDataSourceRequest(BaseUpdateDataSourceRequest):
259
270
  super().__init__(type=DataSourceType.FILE, **data)
260
271
 
261
272
 
273
+ class CodeAnalysisDataSourceRequest(BaseModel):
274
+ """Model for provider-based datasource creation requests"""
275
+
276
+ model_config = ConfigDict(extra="ignore", populate_by_name=True)
277
+
278
+ name: str = Field(..., description="Datasource name")
279
+ description: Optional[str] = Field(None, description="Datasource description")
280
+ project_name: str = Field(..., description="Project name")
281
+ project_space_visible: bool = Field(False, alias="projectSpaceVisible")
282
+ branch: Optional[str] = Field(None, description="Git branch")
283
+ api_url: str = Field(..., description="Repository URL")
284
+ access_token: str = Field(..., description="Access token for repository")
285
+ analyzer: Optional[str] = Field(
286
+ None, description="Code analyzer type (e.g., Java, Python)"
287
+ )
288
+ datasource_root: str = Field("/", description="Root directory to analyze")
289
+
290
+
291
+ class CodeExplorationDataSourceRequest(BaseModel):
292
+ """Model for CodeExplorationToolkit datasource creation requests"""
293
+
294
+ model_config = ConfigDict(extra="ignore", populate_by_name=True)
295
+
296
+ name: str = Field(..., description="Datasource name")
297
+ description: Optional[str] = Field(None, description="Datasource description")
298
+ project_name: str = Field(..., description="Project name")
299
+ project_space_visible: bool = Field(False, alias="projectSpaceVisible")
300
+ code_analysis_datasource_ids: List[str] = Field(
301
+ ...,
302
+ alias="code_analysis_datasource_ids",
303
+ description="List of CodeAnalysisToolkit datasource IDs",
304
+ )
305
+
306
+
262
307
  class DataSource(BaseModel):
263
308
  model_config = ConfigDict(
264
309
  extra="ignore",
@@ -29,7 +29,7 @@ class CredentialTypes(str, Enum):
29
29
  SONAR = "Sonar"
30
30
  SQL = "SQL"
31
31
  TELEGRAM = "Telegram"
32
- ZEPHYR_CLOUD = "ZephyrCloud"
32
+ ZEPHYR_SCALE = "ZephyrScale"
33
33
  ZEPHYR_SQUAD = "ZephyrSquad"
34
34
  SERVICE_NOW = "ServiceNow"
35
35
  DIAL = "DIAL"
@@ -0,0 +1,152 @@
1
+ """Models for vendor guardrail settings."""
2
+
3
+ from datetime import datetime
4
+ from enum import Enum
5
+ from typing import Optional, List
6
+
7
+ from pydantic import BaseModel, ConfigDict, Field
8
+
9
+ from .vendor_assistant import PaginationInfo, TokenPagination
10
+
11
+
12
+ class VendorGuardrailSetting(BaseModel):
13
+ """Model representing a vendor guardrail setting."""
14
+
15
+ model_config = ConfigDict(extra="ignore")
16
+
17
+ setting_id: str = Field(..., description="Unique identifier for the setting")
18
+ setting_name: str = Field(..., description="Name of the setting")
19
+ project: str = Field(..., description="Project associated with the setting")
20
+ entities: List[str] = Field(
21
+ default_factory=list, description="List of entities associated with the setting"
22
+ )
23
+ invalid: Optional[bool] = Field(None, description="Whether the setting is invalid")
24
+ error: Optional[str] = Field(
25
+ None, description="Error message if the setting is invalid"
26
+ )
27
+
28
+
29
+ class VendorGuardrailSettingsResponse(BaseModel):
30
+ """Response model for vendor guardrail settings list."""
31
+
32
+ model_config = ConfigDict(extra="ignore")
33
+
34
+ data: List[VendorGuardrailSetting] = Field(
35
+ ..., description="List of vendor guardrail settings"
36
+ )
37
+ pagination: PaginationInfo = Field(..., description="Pagination information")
38
+
39
+
40
+ class VendorGuardrailStatus(str, Enum):
41
+ """Status of vendor guardrail."""
42
+
43
+ PREPARED = "PREPARED"
44
+ NOT_PREPARED = "NOT_PREPARED"
45
+
46
+
47
+ class VendorGuardrail(BaseModel):
48
+ """Model representing a vendor guardrail."""
49
+
50
+ model_config = ConfigDict(extra="ignore")
51
+
52
+ id: str = Field(..., description="Unique identifier for the guardrail")
53
+ name: str = Field(..., description="Name of the guardrail")
54
+ status: VendorGuardrailStatus = Field(..., description="Status of the guardrail")
55
+ description: Optional[str] = Field(None, description="Description of the guardrail")
56
+ version: str = Field(..., description="Version of the guardrail")
57
+ createdAt: datetime = Field(
58
+ ..., description="Creation timestamp", alias="createdAt"
59
+ )
60
+ updatedAt: datetime = Field(
61
+ ..., description="Last update timestamp", alias="updatedAt"
62
+ )
63
+
64
+
65
+ class VendorGuardrailsResponse(BaseModel):
66
+ """Response model for vendor guardrails list."""
67
+
68
+ model_config = ConfigDict(extra="ignore")
69
+
70
+ data: List[VendorGuardrail] = Field(..., description="List of vendor guardrails")
71
+ pagination: TokenPagination = Field(
72
+ ..., description="Token-based pagination information"
73
+ )
74
+
75
+
76
+ class VendorGuardrailVersion(BaseModel):
77
+ """Model representing a vendor guardrail version."""
78
+
79
+ model_config = ConfigDict(extra="ignore")
80
+
81
+ id: str = Field(..., description="Unique identifier for the guardrail")
82
+ version: str = Field(..., description="Version of the guardrail")
83
+ name: str = Field(..., description="Name of the guardrail")
84
+ status: VendorGuardrailStatus = Field(..., description="Status of the version")
85
+ description: Optional[str] = Field(None, description="Description of the version")
86
+ blockedInputMessaging: Optional[str] = Field(
87
+ None,
88
+ description="Message to display when input is blocked by guardrail",
89
+ alias="blockedInputMessaging",
90
+ )
91
+ blockedOutputsMessaging: Optional[str] = Field(
92
+ None,
93
+ description="Message to display when output is blocked by guardrail",
94
+ alias="blockedOutputsMessaging",
95
+ )
96
+ createdAt: datetime = Field(
97
+ ..., description="Creation timestamp", alias="createdAt"
98
+ )
99
+ updatedAt: datetime = Field(
100
+ ..., description="Last update timestamp", alias="updatedAt"
101
+ )
102
+
103
+
104
+ class VendorGuardrailVersionsResponse(BaseModel):
105
+ """Response model for vendor guardrail versions list."""
106
+
107
+ model_config = ConfigDict(extra="ignore")
108
+
109
+ data: List[VendorGuardrailVersion] = Field(
110
+ ..., description="List of vendor guardrail versions"
111
+ )
112
+ pagination: TokenPagination = Field(
113
+ ..., description="Token-based pagination information"
114
+ )
115
+
116
+
117
+ class VendorGuardrailInstallRequest(BaseModel):
118
+ """Model for a single guardrail installation request."""
119
+
120
+ model_config = ConfigDict(extra="ignore")
121
+
122
+ id: str = Field(..., description="Guardrail ID to install")
123
+ version: str = Field(..., description="Version to use for the guardrail")
124
+ setting_id: str = Field(..., description="Vendor setting ID")
125
+
126
+
127
+ class VendorGuardrailInstallSummary(BaseModel):
128
+ """Model for guardrail installation summary."""
129
+
130
+ model_config = ConfigDict(extra="ignore")
131
+
132
+ guardrailId: str = Field(..., description="Installed guardrail ID")
133
+ version: str = Field(..., description="Version used for installation")
134
+ aiRunId: str = Field(..., description="AI run ID for the installation")
135
+
136
+
137
+ class VendorGuardrailInstallResponse(BaseModel):
138
+ """Response model for guardrail installation."""
139
+
140
+ model_config = ConfigDict(extra="ignore")
141
+
142
+ summary: List[VendorGuardrailInstallSummary] = Field(
143
+ ..., description="List of installation summaries"
144
+ )
145
+
146
+
147
+ class VendorGuardrailUninstallResponse(BaseModel):
148
+ """Response model for guardrail uninstallation."""
149
+
150
+ model_config = ConfigDict(extra="ignore")
151
+
152
+ success: bool = Field(..., description="Whether the uninstallation was successful")
@@ -15,3 +15,7 @@ class WorkflowExecutionCreateRequest(BaseModel):
15
15
  file_name: Optional[str] = Field(
16
16
  None, description="File name associated with the workflow execution"
17
17
  )
18
+ propagate_headers: bool = Field(
19
+ default=False,
20
+ description="Enable propagation of X-* HTTP headers to MCP servers during tool execution",
21
+ )
@@ -170,16 +170,153 @@ class AssistantService:
170
170
  """
171
171
  return self._api.get(f"/v1/assistants/prebuilt/{slug}", Assistant)
172
172
 
173
+ def list_versions(
174
+ self, assistant_id: str, page: int = 0, per_page: Optional[int] = None
175
+ ):
176
+ """List assistant versions.
177
+
178
+ Args:
179
+ assistant_id: Assistant identifier
180
+ page: Page number for pagination
181
+ per_page: Items per page (optional). If not provided, backend defaults are used.
182
+
183
+ Returns:
184
+ List of AssistantVersion objects
185
+ """
186
+
187
+ params: Dict[str, Any] = {"page": page}
188
+ if per_page is not None:
189
+ params["per_page"] = per_page
190
+ from ..models.assistant import AssistantVersion
191
+
192
+ raw = self._api.get(
193
+ f"/v1/assistants/{assistant_id}/versions",
194
+ dict,
195
+ params=params,
196
+ wrap_response=False,
197
+ )
198
+ items = []
199
+ if isinstance(raw, list):
200
+ items = raw
201
+ elif isinstance(raw, dict):
202
+ items = raw.get("data") or raw.get("versions") or []
203
+ else:
204
+ items = []
205
+ return [AssistantVersion.model_validate(it) for it in items]
206
+
207
+ def get_version(self, assistant_id: str, version_number: int):
208
+ """Get a specific assistant version by number.
209
+
210
+ Args:
211
+ assistant_id: Assistant identifier
212
+ version_number: Version number to retrieve
213
+
214
+ Returns:
215
+ AssistantVersion object
216
+ """
217
+ from ..models.assistant import AssistantVersion
218
+
219
+ raw = self._api.get(
220
+ f"/v1/assistants/{assistant_id}/versions/{version_number}", AssistantVersion
221
+ )
222
+ if isinstance(raw, dict):
223
+ return AssistantVersion.model_validate(raw)
224
+ return raw
225
+
226
+ def compare_versions(self, assistant_id: str, v1: int, v2: int) -> Dict[str, Any]:
227
+ """Compare two assistant versions and return diff summary.
228
+
229
+ Args:
230
+ assistant_id: Assistant identifier
231
+ v1: First version number
232
+ v2: Second version number
233
+
234
+ Returns:
235
+ Generic dictionary with comparison result (diff, summary, etc.)
236
+ """
237
+ return self._api.get(
238
+ f"/v1/assistants/{assistant_id}/versions/{v1}/compare/{v2}",
239
+ dict,
240
+ )
241
+
242
+ def rollback_to_version(
243
+ self, assistant_id: str, version_number: int, change_notes: Optional[str] = None
244
+ ) -> dict:
245
+ """Rollback assistant to a specific version. Creates a new version mirroring target.
246
+
247
+ Args:
248
+ assistant_id: Assistant identifier
249
+ version_number: Target version to rollback to
250
+ change_notes: Optional description of why rollback is performed
251
+
252
+ Returns:
253
+ Backend response (dict)
254
+ """
255
+ payload: Dict[str, Any] = {}
256
+ if change_notes:
257
+ payload["change_notes"] = change_notes
258
+ try:
259
+ return self._api.post(
260
+ f"/v1/assistants/{assistant_id}/versions/{version_number}/rollback",
261
+ dict,
262
+ json_data=payload,
263
+ )
264
+ except requests.HTTPError as err:
265
+ try:
266
+ assistant = self.get(assistant_id)
267
+ version = self.get_version(assistant_id, version_number)
268
+
269
+ update_req = AssistantUpdateRequest(
270
+ name=assistant.name,
271
+ description=assistant.description or "",
272
+ system_prompt=version.system_prompt,
273
+ project=assistant.project,
274
+ llm_model_type=version.llm_model_type or assistant.llm_model_type,
275
+ temperature=version.temperature
276
+ if hasattr(version, "temperature")
277
+ else assistant.temperature,
278
+ top_p=version.top_p
279
+ if hasattr(version, "top_p")
280
+ else assistant.top_p,
281
+ context=version.context
282
+ if hasattr(version, "context")
283
+ else assistant.context,
284
+ toolkits=version.toolkits
285
+ if hasattr(version, "toolkits")
286
+ else assistant.toolkits,
287
+ user_prompts=assistant.user_prompts,
288
+ shared=assistant.shared,
289
+ is_react=assistant.is_react,
290
+ is_global=assistant.is_global,
291
+ slug=assistant.slug,
292
+ mcp_servers=version.mcp_servers
293
+ if hasattr(version, "mcp_servers")
294
+ else assistant.mcp_servers,
295
+ assistant_ids=version.assistant_ids
296
+ if hasattr(version, "assistant_ids")
297
+ else assistant.assistant_ids,
298
+ )
299
+ resp = self.update(assistant_id, update_req)
300
+ resp["_rollback_fallback"] = True
301
+ resp["_target_version"] = version_number
302
+ if change_notes:
303
+ resp["change_notes"] = change_notes
304
+ return resp
305
+ except Exception:
306
+ raise err
307
+
173
308
  def chat(
174
309
  self,
175
310
  assistant_id: str,
176
311
  request: AssistantChatRequest,
312
+ headers: Optional[Dict[str, str]] = None,
177
313
  ) -> Union[requests.Response, BaseModelResponse]:
178
314
  """Send a chat request to an assistant.
179
315
 
180
316
  Args:
181
317
  assistant_id: ID of the assistant to chat with
182
318
  request: Chat request details
319
+ headers: Optional additional HTTP headers (e.g., X-* for MCP propagation)
183
320
 
184
321
  Returns:
185
322
  Chat response or streaming response
@@ -198,6 +335,7 @@ class AssistantService:
198
335
  BaseModelResponse,
199
336
  json_data=request.model_dump(exclude_none=True, by_alias=True),
200
337
  stream=request.stream,
338
+ extra_headers=headers,
201
339
  )
202
340
  if not request.stream and pydantic_schema:
203
341
  # we do conversion to the BaseModel here because self._parse_response don't see actual request model,
@@ -206,6 +344,82 @@ class AssistantService:
206
344
 
207
345
  return response
208
346
 
347
+ def chat_with_version(
348
+ self,
349
+ assistant_id: str,
350
+ version_number: int,
351
+ request: AssistantChatRequest,
352
+ ) -> Union[requests.Response, BaseModelResponse]:
353
+ """Send a chat request to a specific assistant version.
354
+
355
+ Uses the stable chat endpoint with an explicit `version` parameter to
356
+ ensure compatibility with environments that don't expose
357
+ /versions/{version}/model.
358
+
359
+ Args:
360
+ assistant_id: ID of the assistant to chat with
361
+ version_number: version to pin chat to
362
+ request: Chat request details
363
+
364
+ Returns:
365
+ Chat response or streaming response
366
+ """
367
+ pydantic_schema = None
368
+ if issubclass(request.output_schema, BaseModel):
369
+ pydantic_schema = deepcopy(request.output_schema)
370
+ request.output_schema = request.output_schema.model_json_schema()
371
+
372
+ payload = request.model_dump(exclude_none=True, by_alias=True)
373
+ payload["version"] = version_number
374
+
375
+ response = self._api.post(
376
+ f"/v1/assistants/{assistant_id}/model",
377
+ BaseModelResponse,
378
+ json_data=payload,
379
+ stream=request.stream,
380
+ )
381
+ if not request.stream and pydantic_schema:
382
+ response.generated = pydantic_schema.model_validate(response.generated)
383
+
384
+ return response
385
+
386
+ def chat_by_slug(
387
+ self,
388
+ assistant_slug: str,
389
+ request: AssistantChatRequest,
390
+ headers: Optional[Dict[str, str]] = None,
391
+ ) -> Union[requests.Response, BaseModelResponse]:
392
+ """Send a chat request to an assistant by slug.
393
+
394
+ Args:
395
+ assistant_slug: Slug of the assistant to chat with
396
+ request: Chat request details
397
+ headers: Optional additional HTTP headers (e.g., X-* for MCP propagation)
398
+
399
+ Returns:
400
+ Chat response or streaming response
401
+ """
402
+ pydantic_schema = None
403
+ if (
404
+ request.output_schema is not None
405
+ and inspect.isclass(request.output_schema)
406
+ and issubclass(request.output_schema, BaseModel)
407
+ ):
408
+ pydantic_schema = deepcopy(request.output_schema)
409
+ request.output_schema = request.output_schema.model_json_schema()
410
+
411
+ response = self._api.post(
412
+ f"/v1/assistants/slug/{assistant_slug}/model",
413
+ BaseModelResponse,
414
+ json_data=request.model_dump(exclude_none=True, by_alias=True),
415
+ stream=request.stream,
416
+ extra_headers=headers,
417
+ )
418
+ if not request.stream and pydantic_schema:
419
+ response.generated = pydantic_schema.model_validate(response.generated)
420
+
421
+ return response
422
+
209
423
  def upload_file_to_chat(self, file_path: Path):
210
424
  """Upload a file to assistant chat and return the response containing file_url."""
211
425