jl-ecms-client 0.2.8__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 jl-ecms-client might be problematic. Click here for more details.

Files changed (53) hide show
  1. jl_ecms_client-0.2.8.dist-info/METADATA +295 -0
  2. jl_ecms_client-0.2.8.dist-info/RECORD +53 -0
  3. jl_ecms_client-0.2.8.dist-info/WHEEL +5 -0
  4. jl_ecms_client-0.2.8.dist-info/licenses/LICENSE +190 -0
  5. jl_ecms_client-0.2.8.dist-info/top_level.txt +1 -0
  6. mirix/client/__init__.py +14 -0
  7. mirix/client/client.py +405 -0
  8. mirix/client/constants.py +60 -0
  9. mirix/client/remote_client.py +1136 -0
  10. mirix/client/utils.py +34 -0
  11. mirix/helpers/__init__.py +1 -0
  12. mirix/helpers/converters.py +429 -0
  13. mirix/helpers/datetime_helpers.py +90 -0
  14. mirix/helpers/json_helpers.py +47 -0
  15. mirix/helpers/message_helpers.py +74 -0
  16. mirix/helpers/tool_rule_solver.py +166 -0
  17. mirix/schemas/__init__.py +1 -0
  18. mirix/schemas/agent.py +401 -0
  19. mirix/schemas/block.py +188 -0
  20. mirix/schemas/cloud_file_mapping.py +29 -0
  21. mirix/schemas/embedding_config.py +114 -0
  22. mirix/schemas/enums.py +69 -0
  23. mirix/schemas/environment_variables.py +82 -0
  24. mirix/schemas/episodic_memory.py +170 -0
  25. mirix/schemas/file.py +57 -0
  26. mirix/schemas/health.py +10 -0
  27. mirix/schemas/knowledge_vault.py +181 -0
  28. mirix/schemas/llm_config.py +187 -0
  29. mirix/schemas/memory.py +318 -0
  30. mirix/schemas/message.py +1315 -0
  31. mirix/schemas/mirix_base.py +107 -0
  32. mirix/schemas/mirix_message.py +411 -0
  33. mirix/schemas/mirix_message_content.py +230 -0
  34. mirix/schemas/mirix_request.py +39 -0
  35. mirix/schemas/mirix_response.py +183 -0
  36. mirix/schemas/openai/__init__.py +1 -0
  37. mirix/schemas/openai/chat_completion_request.py +122 -0
  38. mirix/schemas/openai/chat_completion_response.py +144 -0
  39. mirix/schemas/openai/chat_completions.py +127 -0
  40. mirix/schemas/openai/embedding_response.py +11 -0
  41. mirix/schemas/openai/openai.py +229 -0
  42. mirix/schemas/organization.py +38 -0
  43. mirix/schemas/procedural_memory.py +151 -0
  44. mirix/schemas/providers.py +816 -0
  45. mirix/schemas/resource_memory.py +134 -0
  46. mirix/schemas/sandbox_config.py +132 -0
  47. mirix/schemas/semantic_memory.py +162 -0
  48. mirix/schemas/source.py +96 -0
  49. mirix/schemas/step.py +53 -0
  50. mirix/schemas/tool.py +241 -0
  51. mirix/schemas/tool_rule.py +209 -0
  52. mirix/schemas/usage.py +31 -0
  53. mirix/schemas/user.py +67 -0
mirix/client/utils.py ADDED
@@ -0,0 +1,34 @@
1
+ """
2
+ Lightweight utilities for Mirix client package.
3
+
4
+ Contains only essential utility functions needed by the client,
5
+ without heavy dependencies.
6
+ """
7
+
8
+ import json
9
+ from datetime import datetime, timezone
10
+
11
+
12
+ def get_utc_time() -> datetime:
13
+ """Get the current UTC time"""
14
+ return datetime.now(timezone.utc)
15
+
16
+
17
+ def json_dumps(data, indent=2):
18
+ """
19
+ JSON serializer that handles datetime objects.
20
+
21
+ Args:
22
+ data: Data to serialize
23
+ indent: JSON indentation level
24
+
25
+ Returns:
26
+ str: JSON string
27
+ """
28
+
29
+ def safe_serializer(obj):
30
+ if isinstance(obj, datetime):
31
+ return obj.isoformat()
32
+ raise TypeError(f"Type {type(obj)} not serializable")
33
+
34
+ return json.dumps(data, indent=indent, default=safe_serializer, ensure_ascii=False)
@@ -0,0 +1 @@
1
+ from mirix.helpers.tool_rule_solver import ToolRulesSolver as ToolRulesSolver
@@ -0,0 +1,429 @@
1
+ import base64
2
+ from typing import Any, Dict, List, Optional, Union
3
+
4
+ import numpy as np
5
+ from anthropic.types.beta.messages import (
6
+ BetaMessageBatch,
7
+ BetaMessageBatchIndividualResponse,
8
+ )
9
+ from sqlalchemy import Dialect
10
+
11
+ from mirix.schemas.agent import AgentStepState
12
+ from mirix.schemas.embedding_config import EmbeddingConfig
13
+ from mirix.schemas.enums import ProviderType, ToolRuleType
14
+ from mirix.schemas.llm_config import LLMConfig
15
+ from mirix.schemas.message import ToolReturn
16
+ from mirix.schemas.mirix_message_content import (
17
+ CloudFileContent,
18
+ FileContent,
19
+ ImageContent,
20
+ MessageContent,
21
+ MessageContentType,
22
+ OmittedReasoningContent,
23
+ ReasoningContent,
24
+ RedactedReasoningContent,
25
+ TextContent,
26
+ ToolCallContent,
27
+ ToolReturnContent,
28
+ )
29
+ from mirix.schemas.openai.openai import Function as OpenAIFunction
30
+ from mirix.schemas.openai.openai import ToolCall as OpenAIToolCall
31
+ from mirix.schemas.tool_rule import (
32
+ ChildToolRule,
33
+ ConditionalToolRule,
34
+ ContinueToolRule,
35
+ InitToolRule,
36
+ MaxCountPerStepToolRule,
37
+ ParentToolRule,
38
+ TerminalToolRule,
39
+ ToolRule,
40
+ )
41
+
42
+ # --------------------------
43
+ # LLMConfig Serialization
44
+ # --------------------------
45
+
46
+
47
+ def serialize_llm_config(config: Union[Optional[LLMConfig], Dict]) -> Optional[Dict]:
48
+ """Convert an LLMConfig object into a JSON-serializable dictionary."""
49
+ if config and isinstance(config, LLMConfig):
50
+ return config.model_dump(mode="json")
51
+ return config
52
+
53
+
54
+ def deserialize_llm_config(data: Optional[Dict]) -> Optional[LLMConfig]:
55
+ """Convert a dictionary back into an LLMConfig object."""
56
+ return LLMConfig(**data) if data else None
57
+
58
+
59
+ # --------------------------
60
+ # EmbeddingConfig Serialization
61
+ # --------------------------
62
+
63
+
64
+ def serialize_embedding_config(
65
+ config: Union[Optional[EmbeddingConfig], Dict],
66
+ ) -> Optional[Dict]:
67
+ """Convert an EmbeddingConfig object into a JSON-serializable dictionary."""
68
+ if config and isinstance(config, EmbeddingConfig):
69
+ return config.model_dump(mode="json")
70
+ return config
71
+
72
+
73
+ def deserialize_embedding_config(data: Optional[Dict]) -> Optional[EmbeddingConfig]:
74
+ """Convert a dictionary back into an EmbeddingConfig object."""
75
+ return EmbeddingConfig(**data) if data else None
76
+
77
+
78
+ # --------------------------
79
+ # ToolRule Serialization
80
+ # --------------------------
81
+
82
+
83
+ def serialize_tool_rules(tool_rules: Optional[List[ToolRule]]) -> List[Dict[str, Any]]:
84
+ """Convert a list of ToolRules into a JSON-serializable format."""
85
+
86
+ if not tool_rules:
87
+ return []
88
+
89
+ data = [
90
+ {**rule.model_dump(mode="json"), "type": rule.type.value} for rule in tool_rules
91
+ ] # Convert Enum to string for JSON compatibility
92
+
93
+ # Validate ToolRule structure
94
+ for rule_data in data:
95
+ if (
96
+ rule_data["type"] == ToolRuleType.constrain_child_tools.value
97
+ and "children" not in rule_data
98
+ ):
99
+ raise ValueError(
100
+ f"Invalid ToolRule serialization: 'children' field missing for rule {rule_data}"
101
+ )
102
+
103
+ return data
104
+
105
+
106
+ def deserialize_tool_rules(data: Optional[List[Dict]]) -> List[ToolRule]:
107
+ """Convert a list of dictionaries back into ToolRule objects."""
108
+ if not data:
109
+ return []
110
+
111
+ return [deserialize_tool_rule(rule_data) for rule_data in data]
112
+
113
+
114
+ def deserialize_tool_rule(
115
+ data: Dict,
116
+ ) -> ToolRule:
117
+ """Deserialize a dictionary to the appropriate ToolRule subclass based on 'type'."""
118
+ rule_type = ToolRuleType(data.get("type"))
119
+
120
+ if rule_type == ToolRuleType.run_first:
121
+ data["type"] = ToolRuleType.run_first
122
+ return InitToolRule(**data)
123
+ elif rule_type == ToolRuleType.exit_loop:
124
+ data["type"] = ToolRuleType.exit_loop
125
+ return TerminalToolRule(**data)
126
+ elif rule_type == ToolRuleType.constrain_child_tools:
127
+ data["type"] = ToolRuleType.constrain_child_tools
128
+ return ChildToolRule(**data)
129
+ elif rule_type == ToolRuleType.conditional:
130
+ return ConditionalToolRule(**data)
131
+ elif rule_type == ToolRuleType.continue_loop:
132
+ return ContinueToolRule(**data)
133
+ elif rule_type == ToolRuleType.max_count_per_step:
134
+ return MaxCountPerStepToolRule(**data)
135
+ elif rule_type == ToolRuleType.parent_last_tool:
136
+ return ParentToolRule(**data)
137
+ raise ValueError(f"Unknown ToolRule type: {rule_type}")
138
+
139
+
140
+ # --------------------------
141
+ # ToolCall Serialization
142
+ # --------------------------
143
+
144
+
145
+ def serialize_tool_calls(
146
+ tool_calls: Optional[List[Union[OpenAIToolCall, dict]]],
147
+ ) -> List[Dict]:
148
+ """Convert a list of OpenAI ToolCall objects into JSON-serializable format."""
149
+ if not tool_calls:
150
+ return []
151
+
152
+ serialized_calls = []
153
+ for call in tool_calls:
154
+ if isinstance(call, OpenAIToolCall):
155
+ serialized_calls.append(call.model_dump(mode="json"))
156
+ elif isinstance(call, dict):
157
+ serialized_calls.append(call) # Already a dictionary, leave it as-is
158
+ else:
159
+ raise TypeError(f"Unexpected tool call type: {type(call)}")
160
+
161
+ return serialized_calls
162
+
163
+
164
+ def deserialize_tool_calls(data: Optional[List[Dict]]) -> List[OpenAIToolCall]:
165
+ """Convert a JSON list back into OpenAIToolCall objects."""
166
+ if not data:
167
+ return []
168
+
169
+ calls = []
170
+ for item in data:
171
+ func_data = item.pop("function", None)
172
+ tool_call_function = OpenAIFunction(**func_data)
173
+ calls.append(OpenAIToolCall(function=tool_call_function, **item))
174
+
175
+ return calls
176
+
177
+
178
+ # --------------------------
179
+ # ToolReturn Serialization
180
+ # --------------------------
181
+
182
+
183
+ def serialize_tool_returns(
184
+ tool_returns: Optional[List[Union[ToolReturn, dict]]],
185
+ ) -> List[Dict]:
186
+ """Convert a list of ToolReturn objects into JSON-serializable format."""
187
+ if not tool_returns:
188
+ return []
189
+
190
+ serialized_tool_returns = []
191
+ for tool_return in tool_returns:
192
+ if isinstance(tool_return, ToolReturn):
193
+ serialized_tool_returns.append(tool_return.model_dump(mode="json"))
194
+ elif isinstance(tool_return, dict):
195
+ serialized_tool_returns.append(
196
+ tool_return
197
+ ) # Already a dictionary, leave it as-is
198
+ else:
199
+ raise TypeError(f"Unexpected tool return type: {type(tool_return)}")
200
+
201
+ return serialized_tool_returns
202
+
203
+
204
+ def deserialize_tool_returns(data: Optional[List[Dict]]) -> List[ToolReturn]:
205
+ """Convert a JSON list back into ToolReturn objects."""
206
+ if not data:
207
+ return []
208
+
209
+ tool_returns = []
210
+ for item in data:
211
+ tool_return = ToolReturn(**item)
212
+ tool_returns.append(tool_return)
213
+
214
+ return tool_returns
215
+
216
+
217
+ # ----------------------------
218
+ # MessageContent Serialization
219
+ # ----------------------------
220
+
221
+
222
+ def serialize_message_content(
223
+ message_content: Optional[List[Union[MessageContent, dict]]],
224
+ ) -> List[Dict]:
225
+ """Convert a list of MessageContent objects into JSON-serializable format."""
226
+ if not message_content:
227
+ return []
228
+
229
+ serialized_message_content = []
230
+ for content in message_content:
231
+ if isinstance(content, MessageContent):
232
+ serialized_message_content.append(content.model_dump(mode="json"))
233
+ elif isinstance(content, dict):
234
+ serialized_message_content.append(
235
+ content
236
+ ) # Already a dictionary, leave it as-is
237
+ else:
238
+ raise TypeError(f"Unexpected message content type: {type(content)}")
239
+
240
+ return serialized_message_content
241
+
242
+
243
+ def deserialize_message_content(data: Optional[List[Dict]]) -> List[MessageContent]:
244
+ """Convert a JSON list back into MessageContent objects."""
245
+ if not data:
246
+ return []
247
+
248
+ message_content = []
249
+ for item in data:
250
+ if not item:
251
+ continue
252
+
253
+ content_type = item.get("type")
254
+ if content_type == MessageContentType.text:
255
+ content = TextContent(**item)
256
+ elif content_type == MessageContentType.image_url:
257
+ content = ImageContent(**item)
258
+ elif content_type == MessageContentType.file_uri:
259
+ content = FileContent(**item)
260
+ elif content_type == MessageContentType.google_cloud_file_uri:
261
+ content = CloudFileContent(**item)
262
+ elif content_type == MessageContentType.tool_call:
263
+ content = ToolCallContent(**item)
264
+ elif content_type == MessageContentType.tool_return:
265
+ content = ToolReturnContent(**item)
266
+ elif content_type == MessageContentType.reasoning:
267
+ content = ReasoningContent(**item)
268
+ elif content_type == MessageContentType.redacted_reasoning:
269
+ content = RedactedReasoningContent(**item)
270
+ elif content_type == MessageContentType.omitted_reasoning:
271
+ content = OmittedReasoningContent(**item)
272
+ else:
273
+ # Skip invalid content
274
+ continue
275
+
276
+ message_content.append(content)
277
+
278
+ return message_content
279
+
280
+
281
+ # --------------------------
282
+ # Vector Serialization
283
+ # --------------------------
284
+
285
+
286
+ def serialize_vector(
287
+ vector: Optional[Union[List[float], np.ndarray]],
288
+ ) -> Optional[bytes]:
289
+ """Convert a NumPy array or list into a base64-encoded byte string."""
290
+ if vector is None:
291
+ return None
292
+ if isinstance(vector, list):
293
+ vector = np.array(vector, dtype=np.float32)
294
+
295
+ return base64.b64encode(vector.tobytes())
296
+
297
+
298
+ def deserialize_vector(data: Optional[bytes], dialect: Dialect) -> Optional[np.ndarray]:
299
+ """Convert a base64-encoded byte string back into a NumPy array."""
300
+ if not data:
301
+ return None
302
+
303
+ if dialect.name == "sqlite":
304
+ data = base64.b64decode(data)
305
+
306
+ return np.frombuffer(data, dtype=np.float32)
307
+
308
+
309
+ # --------------------------
310
+ # Batch Request Serialization
311
+ # --------------------------
312
+
313
+
314
+ def serialize_create_batch_response(
315
+ create_batch_response: Union[BetaMessageBatch],
316
+ ) -> Dict[str, Any]:
317
+ """Convert a list of ToolRules into a JSON-serializable format."""
318
+ llm_provider_type = None
319
+ if isinstance(create_batch_response, BetaMessageBatch):
320
+ llm_provider_type = ProviderType.anthropic.value
321
+
322
+ if not llm_provider_type:
323
+ raise ValueError(
324
+ f"Could not determine llm provider from create batch response object type: {create_batch_response}"
325
+ )
326
+
327
+ return {
328
+ "data": create_batch_response.model_dump(mode="json"),
329
+ "type": llm_provider_type,
330
+ }
331
+
332
+
333
+ def deserialize_create_batch_response(data: Dict) -> Union[BetaMessageBatch]:
334
+ provider_type = ProviderType(data.get("type"))
335
+
336
+ if provider_type == ProviderType.anthropic:
337
+ return BetaMessageBatch(**data.get("data"))
338
+
339
+ raise ValueError(f"Unknown ProviderType type: {provider_type}")
340
+
341
+
342
+ # TODO: Note that this is the same as above for Anthropic, but this is not the case for all providers
343
+ # TODO: Some have different types based on the create v.s. poll requests
344
+ def serialize_poll_batch_response(
345
+ poll_batch_response: Optional[Union[BetaMessageBatch]],
346
+ ) -> Optional[Dict[str, Any]]:
347
+ """Convert a list of ToolRules into a JSON-serializable format."""
348
+ if not poll_batch_response:
349
+ return None
350
+
351
+ llm_provider_type = None
352
+ if isinstance(poll_batch_response, BetaMessageBatch):
353
+ llm_provider_type = ProviderType.anthropic.value
354
+
355
+ if not llm_provider_type:
356
+ raise ValueError(
357
+ f"Could not determine llm provider from poll batch response object type: {poll_batch_response}"
358
+ )
359
+
360
+ return {
361
+ "data": poll_batch_response.model_dump(mode="json"),
362
+ "type": llm_provider_type,
363
+ }
364
+
365
+
366
+ def deserialize_poll_batch_response(
367
+ data: Optional[Dict],
368
+ ) -> Optional[Union[BetaMessageBatch]]:
369
+ if not data:
370
+ return None
371
+
372
+ provider_type = ProviderType(data.get("type"))
373
+
374
+ if provider_type == ProviderType.anthropic:
375
+ return BetaMessageBatch(**data.get("data"))
376
+
377
+ raise ValueError(f"Unknown ProviderType type: {provider_type}")
378
+
379
+
380
+ def serialize_batch_request_result(
381
+ batch_individual_response: Optional[Union[BetaMessageBatchIndividualResponse]],
382
+ ) -> Optional[Dict[str, Any]]:
383
+ """Convert a list of ToolRules into a JSON-serializable format."""
384
+ if not batch_individual_response:
385
+ return None
386
+
387
+ llm_provider_type = None
388
+ if isinstance(batch_individual_response, BetaMessageBatchIndividualResponse):
389
+ llm_provider_type = ProviderType.anthropic.value
390
+
391
+ if not llm_provider_type:
392
+ raise ValueError(
393
+ f"Could not determine llm provider from batch result object type: {batch_individual_response}"
394
+ )
395
+
396
+ return {
397
+ "data": batch_individual_response.model_dump(mode="json"),
398
+ "type": llm_provider_type,
399
+ }
400
+
401
+
402
+ def deserialize_batch_request_result(
403
+ data: Optional[Dict],
404
+ ) -> Optional[Union[BetaMessageBatchIndividualResponse]]:
405
+ if not data:
406
+ return None
407
+ provider_type = ProviderType(data.get("type"))
408
+
409
+ if provider_type == ProviderType.anthropic:
410
+ return BetaMessageBatchIndividualResponse(**data.get("data"))
411
+
412
+ raise ValueError(f"Unknown ProviderType type: {provider_type}")
413
+
414
+
415
+ def serialize_agent_step_state(
416
+ agent_step_state: Optional[AgentStepState],
417
+ ) -> Optional[Dict[str, Any]]:
418
+ """Convert a list of ToolRules into a JSON-serializable format."""
419
+ if not agent_step_state:
420
+ return None
421
+
422
+ return agent_step_state.model_dump(mode="json")
423
+
424
+
425
+ def deserialize_agent_step_state(data: Optional[Dict]) -> Optional[AgentStepState]:
426
+ if not data:
427
+ return None
428
+
429
+ return AgentStepState(**data)
@@ -0,0 +1,90 @@
1
+ import re
2
+ from datetime import datetime, timedelta, timezone
3
+
4
+ import pytz
5
+
6
+
7
+ def parse_formatted_time(formatted_time):
8
+ # parse times returned by letta.utils.get_formatted_time()
9
+ return datetime.strptime(formatted_time, "%Y-%m-%d %I:%M:%S %p %Z%z")
10
+
11
+
12
+ def datetime_to_timestamp(dt):
13
+ # convert datetime object to integer timestamp
14
+ return int(dt.timestamp())
15
+
16
+
17
+ def timestamp_to_datetime(ts):
18
+ # convert integer timestamp to datetime object
19
+ return datetime.fromtimestamp(ts)
20
+
21
+
22
+ def get_local_time_military():
23
+ # Get the current time in UTC
24
+ current_time_utc = datetime.now(pytz.utc)
25
+
26
+ # Convert to San Francisco's time zone (PST/PDT)
27
+ sf_time_zone = pytz.timezone("America/Los_Angeles")
28
+ local_time = current_time_utc.astimezone(sf_time_zone)
29
+
30
+ # You may format it as you desire
31
+ formatted_time = local_time.strftime("%Y-%m-%d %H:%M:%S %Z%z")
32
+
33
+ return formatted_time
34
+
35
+
36
+ def get_local_time_timezone(timezone="America/Los_Angeles"):
37
+ # Get the current time in UTC
38
+ current_time_utc = datetime.now(pytz.utc)
39
+
40
+ # Convert to San Francisco's time zone (PST/PDT)
41
+ sf_time_zone = pytz.timezone(timezone)
42
+ local_time = current_time_utc.astimezone(sf_time_zone)
43
+
44
+ # You may format it as you desire, including AM/PM
45
+ formatted_time = local_time.strftime("%Y-%m-%d %I:%M:%S %p %Z%z")
46
+
47
+ return formatted_time
48
+
49
+
50
+ def get_local_time(timezone=None):
51
+ if timezone is not None:
52
+ time_str = get_local_time_timezone(timezone)
53
+ else:
54
+ # Get the current time, which will be in the local timezone of the computer
55
+ local_time = datetime.now().astimezone()
56
+
57
+ # You may format it as you desire, including AM/PM
58
+ time_str = local_time.strftime("%Y-%m-%d %I:%M:%S %p %Z%z")
59
+
60
+ return time_str.strip()
61
+
62
+
63
+ def get_utc_time() -> datetime:
64
+ """Get the current UTC time"""
65
+ # return datetime.now(pytz.utc)
66
+ return datetime.now(timezone.utc)
67
+
68
+
69
+ def format_datetime(dt):
70
+ return dt.strftime("%Y-%m-%d %I:%M:%S %p %Z%z")
71
+
72
+
73
+ def validate_date_format(date_str):
74
+ """Validate the given date string in the format 'YYYY-MM-DD'."""
75
+ try:
76
+ datetime.strptime(date_str, "%Y-%m-%d")
77
+ return True
78
+ except (ValueError, TypeError):
79
+ return False
80
+
81
+
82
+ def extract_date_from_timestamp(timestamp):
83
+ """Extracts and returns the date from the given timestamp."""
84
+ # Extracts the date (ignoring the time and timezone)
85
+ match = re.match(r"(\d{4}-\d{2}-\d{2})", timestamp)
86
+ return match.group(1) if match else None
87
+
88
+
89
+ def is_utc_datetime(dt: datetime) -> bool:
90
+ return dt.tzinfo is not None and dt.tzinfo.utcoffset(dt) == timedelta(0)
@@ -0,0 +1,47 @@
1
+ import json
2
+ from datetime import datetime
3
+
4
+ import demjson3 as demjson
5
+
6
+ from mirix.log import get_logger
7
+
8
+ logger = get_logger(__name__)
9
+
10
+
11
+ def json_loads(data):
12
+ return json.loads(data, strict=False)
13
+
14
+
15
+ def json_dumps(data, indent=2):
16
+ def safe_serializer(obj):
17
+ if isinstance(obj, datetime):
18
+ return obj.isoformat()
19
+ raise TypeError(f"Type {type(obj)} not serializable")
20
+
21
+ return json.dumps(data, indent=indent, default=safe_serializer, ensure_ascii=False)
22
+
23
+
24
+ def parse_json(string) -> dict:
25
+ """Parse JSON string into JSON with both json and demjson"""
26
+ result = None
27
+ try:
28
+ result = json_loads(string)
29
+ return result
30
+ except Exception as e:
31
+ logger.debug("Error parsing json with json package: %s", e)
32
+
33
+ try:
34
+ result = demjson.decode(string)
35
+ return result
36
+ except demjson.JSONDecodeError as e:
37
+ logger.debug("Error parsing json with demjson package: %s", e)
38
+
39
+ try:
40
+ from json_repair import repair_json
41
+ string = repair_json(string)
42
+ result = json_loads(string)
43
+ return result
44
+
45
+ except Exception as e:
46
+ logger.debug("Error repairing json with json_repair package: %s", e)
47
+ raise e
@@ -0,0 +1,74 @@
1
+ from mirix import system
2
+ from mirix.schemas.enums import MessageRole
3
+ from mirix.schemas.message import Message, MessageCreate
4
+ from mirix.schemas.mirix_message_content import (
5
+ CloudFileContent,
6
+ FileContent,
7
+ ImageContent,
8
+ TextContent,
9
+ )
10
+
11
+
12
+ def extract_and_wrap_message_content(
13
+ role: MessageRole,
14
+ content: str | TextContent | ImageContent | FileContent | CloudFileContent,
15
+ wrap_user_message: bool = False,
16
+ wrap_system_message: bool = True,
17
+ ):
18
+ # Extract message content
19
+ if isinstance(content, str):
20
+ message_content = content
21
+ elif content and isinstance(content, TextContent):
22
+ message_content = content.text
23
+ elif content and isinstance(content, ImageContent):
24
+ # Skip wrapping if the content is an image
25
+ return content
26
+ elif content and isinstance(content, FileContent):
27
+ # Skip wrapping if the content is a file
28
+ return content
29
+ elif content and isinstance(content, CloudFileContent):
30
+ return content
31
+ else:
32
+ raise ValueError("Message content is empty or invalid")
33
+
34
+ # Apply wrapping if needed
35
+ if role == MessageRole.user and wrap_user_message:
36
+ message_content = system.package_user_message(user_message=message_content)
37
+ elif role == MessageRole.system and wrap_system_message:
38
+ message_content = system.package_system_message(system_message=message_content)
39
+ elif role not in {MessageRole.user, MessageRole.system}:
40
+ raise ValueError(f"Invalid message role: {role}")
41
+ return TextContent(text=message_content)
42
+
43
+
44
+ def prepare_input_message_create(
45
+ message: MessageCreate, agent_id: str, **kwargs
46
+ ) -> Message:
47
+ """Converts a MessageCreate object into a Message object, applying wrapping if needed."""
48
+ # TODO: This seems like extra boilerplate with little benefit
49
+ assert isinstance(message, MessageCreate)
50
+
51
+ if isinstance(message.content, list):
52
+ content = [
53
+ extract_and_wrap_message_content(role=message.role, content=c, **kwargs)
54
+ for c in message.content
55
+ ]
56
+ else:
57
+ content = [
58
+ extract_and_wrap_message_content(
59
+ role=message.role, content=message.content, **kwargs
60
+ )
61
+ ]
62
+
63
+ return Message(
64
+ agent_id=agent_id,
65
+ role=message.role,
66
+ content=content,
67
+ name=message.name,
68
+ model=None, # assigned later?
69
+ tool_calls=None, # irrelevant
70
+ tool_call_id=None,
71
+ otid=message.otid,
72
+ sender_id=message.sender_id,
73
+ group_id=message.group_id,
74
+ )