jl-ecms-client 0.2.5__py3-none-any.whl → 0.2.19__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.
mirix/log.py ADDED
@@ -0,0 +1,163 @@
1
+ import logging
2
+ import os
3
+ import sys
4
+ from logging.handlers import RotatingFileHandler
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ from mirix.settings import settings
9
+
10
+
11
+ def get_log_level() -> int:
12
+ """Get the configured log level."""
13
+ if settings.debug:
14
+ return logging.DEBUG
15
+
16
+ # Map string level to logging constant
17
+ level_map = {
18
+ "DEBUG": logging.DEBUG,
19
+ "INFO": logging.INFO,
20
+ "WARNING": logging.WARNING,
21
+ "ERROR": logging.ERROR,
22
+ "CRITICAL": logging.CRITICAL,
23
+ }
24
+
25
+ return level_map.get(settings.log_level.upper(), logging.INFO)
26
+
27
+
28
+ selected_log_level = get_log_level()
29
+
30
+
31
+ def validate_log_file_path(log_file_path: Path) -> Path:
32
+ """
33
+ Validate that the log file path is writable.
34
+
35
+ Checks:
36
+ - Path is not a directory
37
+ - Parent directory exists or can be created
38
+ - We have write permissions to the directory
39
+
40
+ Args:
41
+ log_file_path: Path to the log file
42
+
43
+ Returns:
44
+ Path: Validated absolute path
45
+
46
+ Raises:
47
+ ValueError: If the path is invalid or not writable
48
+ """
49
+ # Convert to absolute path
50
+ log_file_path = log_file_path.expanduser().resolve()
51
+
52
+ # Check if path exists and is a directory (not allowed)
53
+ if log_file_path.exists() and log_file_path.is_dir():
54
+ raise ValueError(
55
+ f"Invalid log file path: '{log_file_path}' is a directory. "
56
+ f"MIRIX_LOG_FILE must be a file path, not a directory."
57
+ )
58
+
59
+ # Get parent directory
60
+ parent_dir = log_file_path.parent
61
+
62
+ # Try to create parent directory if it doesn't exist
63
+ try:
64
+ parent_dir.mkdir(parents=True, exist_ok=True)
65
+ except (OSError, PermissionError) as e:
66
+ raise ValueError(
67
+ f"Invalid log file path: Cannot create directory '{parent_dir}'. "
68
+ f"Error: {e}"
69
+ ) from e
70
+
71
+ # Check if parent directory is writable
72
+ if not os.access(parent_dir, os.W_OK):
73
+ raise ValueError(
74
+ f"Invalid log file path: Directory '{parent_dir}' is not writable. "
75
+ f"Check permissions for MIRIX_LOG_FILE."
76
+ )
77
+
78
+ # If file exists, check if it's writable
79
+ if log_file_path.exists() and not os.access(log_file_path, os.W_OK):
80
+ raise ValueError(
81
+ f"Invalid log file path: File '{log_file_path}' exists but is not writable. "
82
+ f"Check file permissions for MIRIX_LOG_FILE."
83
+ )
84
+
85
+ return log_file_path
86
+
87
+
88
+ def get_logger(name: Optional[str] = None) -> "logging.Logger":
89
+ """
90
+ Get the Mirix logger with configured handlers.
91
+
92
+ Log Level Configuration:
93
+ - Single log level (MIRIX_LOG_LEVEL) applies to ALL handlers
94
+ - Controlled by: MIRIX_LOG_LEVEL or MIRIX_DEBUG environment variables
95
+ - Same level used for both console and file output
96
+
97
+ Handler Configuration (Default Behavior):
98
+ - Console: ALWAYS enabled UNLESS explicitly disabled (MIRIX_LOG_TO_CONSOLE=false)
99
+ - File: Automatically enabled if MIRIX_LOG_FILE is set with a valid path
100
+ - Handlers determine WHERE logs go, NOT what level they use
101
+
102
+ Returns:
103
+ logging.Logger: Configured logger instance
104
+
105
+ Raises:
106
+ ValueError: If MIRIX_LOG_FILE is set but the path is invalid or not writable
107
+ """
108
+ logger = logging.getLogger("Mirix")
109
+
110
+ # Set the log level ONCE for the entire logger
111
+ # This single level applies to all handlers (console and file)
112
+ logger.setLevel(selected_log_level)
113
+
114
+ # Add handlers if not already configured
115
+ # Handlers control WHERE logs go (console/file), not WHAT level they use
116
+ if not logger.handlers:
117
+ # Create a single formatter for consistency across all handlers
118
+ formatter = logging.Formatter(
119
+ '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
120
+ datefmt='%Y-%m-%d %H:%M:%S'
121
+ )
122
+
123
+ handlers_added = []
124
+
125
+ # Console handler - ALWAYS enabled unless explicitly disabled
126
+ # Console logging is the default behavior
127
+ if settings.log_to_console:
128
+ console_handler = logging.StreamHandler(sys.stdout)
129
+ console_handler.setFormatter(formatter)
130
+ logger.addHandler(console_handler)
131
+ handlers_added.append("console")
132
+
133
+ # File handler - ONLY enabled if MIRIX_LOG_FILE is configured
134
+ # Automatically enabled when MIRIX_LOG_FILE is set
135
+ if settings.log_file is not None:
136
+ # Validate and get absolute path
137
+ # This will raise ValueError if path is invalid
138
+ log_file = validate_log_file_path(Path(settings.log_file))
139
+
140
+ # Create rotating file handler
141
+ file_handler = RotatingFileHandler(
142
+ log_file,
143
+ maxBytes=settings.log_max_bytes,
144
+ backupCount=settings.log_backup_count,
145
+ )
146
+ file_handler.setFormatter(formatter)
147
+ logger.addHandler(file_handler)
148
+ handlers_added.append(f"file ({log_file})")
149
+
150
+ # Log where logs are being written (if any handlers were added)
151
+ if handlers_added:
152
+ destinations = " and ".join(handlers_added)
153
+ log_level_name = logging.getLevelName(selected_log_level)
154
+ logger.info("Logging to: %s (level: %s)", destinations, log_level_name)
155
+ else:
156
+ # No handlers configured - add NullHandler to prevent warnings
157
+ # This only happens if console is explicitly disabled AND file is not configured
158
+ logger.addHandler(logging.NullHandler())
159
+
160
+ # Prevent propagation to root logger to avoid duplicate messages
161
+ logger.propagate = False
162
+
163
+ return logger
mirix/schemas/agent.py CHANGED
@@ -14,7 +14,8 @@ from mirix.schemas.mirix_base import OrmMetadataBase
14
14
  from mirix.schemas.openai.chat_completion_response import UsageStatistics
15
15
  from mirix.schemas.tool import Tool
16
16
  from mirix.schemas.tool_rule import ToolRule
17
- from mirix.utils import create_random_username
17
+
18
+ # Removed create_random_username import - server generates names if not provided
18
19
 
19
20
 
20
21
  class AgentType(str, Enum):
@@ -108,9 +109,9 @@ class AgentState(OrmMetadataBase, validate_assignment=True):
108
109
 
109
110
  class CreateAgent(BaseModel, validate_assignment=True): #
110
111
  # all optional as server can generate defaults
111
- name: str = Field(
112
- default_factory=lambda: create_random_username(),
113
- description="The name of the agent.",
112
+ name: Optional[str] = Field(
113
+ None,
114
+ description="The name of the agent. If not provided, server will generate one.",
114
115
  )
115
116
 
116
117
  # memory creation
@@ -2,8 +2,8 @@ from datetime import datetime
2
2
 
3
3
  from pydantic import Field
4
4
 
5
+ from mirix.client.utils import get_utc_time
5
6
  from mirix.schemas.mirix_base import MirixBase
6
- from mirix.utils import get_utc_time
7
7
 
8
8
 
9
9
  class CloudFileMappingBase(MirixBase):
@@ -2,9 +2,6 @@ from typing import Literal, Optional
2
2
 
3
3
  from pydantic import BaseModel, Field
4
4
 
5
- from mirix.log import get_logger
6
-
7
- logger = get_logger(__name__)
8
5
 
9
6
  class EmbeddingConfig(BaseModel):
10
7
  """
mirix/schemas/enums.py CHANGED
@@ -1,6 +1,18 @@
1
1
  from enum import Enum
2
2
 
3
3
 
4
+ class ToolType(str, Enum):
5
+ """Types of tools in Mirix"""
6
+ CUSTOM = "custom"
7
+ MIRIX_CORE = "mirix_core"
8
+ MIRIX_CODER_CORE = "mirix_coder_core"
9
+ MIRIX_MEMORY_CORE = "mirix_memory_core"
10
+ MIRIX_EXTRA = "mirix_extra"
11
+ MIRIX_MCP = "mirix_mcp"
12
+ MIRIX_MULTI_AGENT_CORE = "mirix_multi_agent_core"
13
+ USER_DEFINED = "user_defined"
14
+
15
+
4
16
  class ProviderType(str, Enum):
5
17
  anthropic = "anthropic"
6
18
 
@@ -3,10 +3,10 @@ from typing import Any, Dict, List, Optional
3
3
 
4
4
  from pydantic import Field, field_validator
5
5
 
6
+ from mirix.client.utils import get_utc_time
6
7
  from mirix.constants import MAX_EMBEDDING_DIM
7
8
  from mirix.schemas.embedding_config import EmbeddingConfig
8
9
  from mirix.schemas.mirix_base import MirixBase
9
- from mirix.utils import get_utc_time
10
10
 
11
11
 
12
12
  class EpisodicEventBase(MirixBase):
@@ -3,10 +3,10 @@ from typing import Any, Dict, List, Optional
3
3
 
4
4
  from pydantic import Field, field_validator
5
5
 
6
+ from mirix.client.utils import get_utc_time
6
7
  from mirix.constants import MAX_EMBEDDING_DIM
7
8
  from mirix.schemas.embedding_config import EmbeddingConfig
8
9
  from mirix.schemas.mirix_base import MirixBase
9
- from mirix.utils import get_utc_time
10
10
 
11
11
 
12
12
  class KnowledgeVaultItemBase(MirixBase):
@@ -5,10 +5,10 @@ from typing import List, Union
5
5
 
6
6
  from pydantic import BaseModel, Field
7
7
 
8
+ from mirix.client.utils import json_dumps
8
9
  from mirix.schemas.enums import MessageStreamStatus
9
10
  from mirix.schemas.mirix_message import MirixMessage, MirixMessageUnion
10
11
  from mirix.schemas.usage import MirixUsageStatistics
11
- from mirix.utils import json_dumps
12
12
 
13
13
  # TODO: consider moving into own file
14
14
 
@@ -1,11 +1,11 @@
1
+ import uuid
1
2
  from datetime import datetime
2
3
  from typing import Optional
3
- import uuid
4
4
 
5
5
  from pydantic import Field
6
6
 
7
+ from mirix.client.utils import get_utc_time
7
8
  from mirix.schemas.mirix_base import MirixBase
8
- from mirix.utils import create_random_username, get_utc_time
9
9
 
10
10
 
11
11
  class OrganizationBase(MirixBase):
@@ -22,9 +22,9 @@ class Organization(OrganizationBase):
22
22
  default_factory=_generate_org_id,
23
23
  description="The unique identifier of the organization.",
24
24
  )
25
- name: str = Field(
26
- create_random_username(),
27
- description="The name of the organization.",
25
+ name: Optional[str] = Field(
26
+ None,
27
+ description="The name of the organization. Server will generate if not provided.",
28
28
  json_schema_extra={"default": "SincereYogurt"},
29
29
  )
30
30
  created_at: Optional[datetime] = Field(
@@ -3,10 +3,10 @@ from typing import Any, Dict, List, Optional
3
3
 
4
4
  from pydantic import Field, field_validator
5
5
 
6
+ from mirix.client.utils import get_utc_time
6
7
  from mirix.constants import MAX_EMBEDDING_DIM
7
8
  from mirix.schemas.embedding_config import EmbeddingConfig
8
9
  from mirix.schemas.mirix_base import MirixBase
9
- from mirix.utils import get_utc_time
10
10
 
11
11
 
12
12
  class ProceduralMemoryItemBase(MirixBase):
@@ -6,7 +6,7 @@ from pydantic import Field, field_validator
6
6
  from mirix.constants import MAX_EMBEDDING_DIM
7
7
  from mirix.schemas.embedding_config import EmbeddingConfig
8
8
  from mirix.schemas.mirix_base import MirixBase
9
- from mirix.utils import get_utc_time
9
+ from mirix.client.utils import get_utc_time
10
10
 
11
11
 
12
12
  class ResourceMemoryItemBase(MirixBase):
@@ -8,12 +8,9 @@ from pydantic import BaseModel, Field, model_validator
8
8
  from mirix.schemas.agent import AgentState
9
9
  from mirix.schemas.mirix_base import MirixBase, OrmMetadataBase
10
10
  from mirix.settings import tool_settings
11
- from mirix.log import get_logger
12
11
 
13
12
 
14
13
  # Sandbox Config
15
-
16
- logger = get_logger(__name__)
17
14
  class SandboxType(str, Enum):
18
15
  E2B = "e2b"
19
16
  LOCAL = "local"
@@ -73,6 +70,7 @@ class E2BSandboxConfig(BaseModel):
73
70
  Assign a default template value if the template field is not provided.
74
71
  """
75
72
  if data.get("template") is None:
73
+ # Use configured template if available
76
74
  data["template"] = tool_settings.e2b_sandbox_template_id
77
75
  return data
78
76
 
@@ -6,7 +6,7 @@ from pydantic import Field, field_validator
6
6
  from mirix.constants import MAX_EMBEDDING_DIM
7
7
  from mirix.schemas.embedding_config import EmbeddingConfig
8
8
  from mirix.schemas.mirix_base import MirixBase
9
- from mirix.utils import get_utc_time
9
+ from mirix.client.utils import get_utc_time
10
10
 
11
11
 
12
12
  class SemanticMemoryItemBase(MirixBase):