jl-ecms-client 0.2.0__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 (51) hide show
  1. jl_ecms_client-0.2.0.dist-info/METADATA +295 -0
  2. jl_ecms_client-0.2.0.dist-info/RECORD +51 -0
  3. jl_ecms_client-0.2.0.dist-info/WHEEL +5 -0
  4. jl_ecms_client-0.2.0.dist-info/licenses/LICENSE +190 -0
  5. jl_ecms_client-0.2.0.dist-info/top_level.txt +1 -0
  6. mirix/client/__init__.py +72 -0
  7. mirix/client/client.py +2594 -0
  8. mirix/client/remote_client.py +1136 -0
  9. mirix/helpers/__init__.py +1 -0
  10. mirix/helpers/converters.py +429 -0
  11. mirix/helpers/datetime_helpers.py +90 -0
  12. mirix/helpers/json_helpers.py +47 -0
  13. mirix/helpers/message_helpers.py +74 -0
  14. mirix/helpers/tool_rule_solver.py +166 -0
  15. mirix/schemas/__init__.py +1 -0
  16. mirix/schemas/agent.py +400 -0
  17. mirix/schemas/block.py +188 -0
  18. mirix/schemas/cloud_file_mapping.py +29 -0
  19. mirix/schemas/embedding_config.py +114 -0
  20. mirix/schemas/enums.py +69 -0
  21. mirix/schemas/environment_variables.py +82 -0
  22. mirix/schemas/episodic_memory.py +170 -0
  23. mirix/schemas/file.py +57 -0
  24. mirix/schemas/health.py +10 -0
  25. mirix/schemas/knowledge_vault.py +181 -0
  26. mirix/schemas/llm_config.py +187 -0
  27. mirix/schemas/memory.py +318 -0
  28. mirix/schemas/message.py +1315 -0
  29. mirix/schemas/mirix_base.py +107 -0
  30. mirix/schemas/mirix_message.py +411 -0
  31. mirix/schemas/mirix_message_content.py +230 -0
  32. mirix/schemas/mirix_request.py +39 -0
  33. mirix/schemas/mirix_response.py +183 -0
  34. mirix/schemas/openai/__init__.py +1 -0
  35. mirix/schemas/openai/chat_completion_request.py +122 -0
  36. mirix/schemas/openai/chat_completion_response.py +144 -0
  37. mirix/schemas/openai/chat_completions.py +127 -0
  38. mirix/schemas/openai/embedding_response.py +11 -0
  39. mirix/schemas/openai/openai.py +229 -0
  40. mirix/schemas/organization.py +38 -0
  41. mirix/schemas/procedural_memory.py +151 -0
  42. mirix/schemas/providers.py +816 -0
  43. mirix/schemas/resource_memory.py +134 -0
  44. mirix/schemas/sandbox_config.py +132 -0
  45. mirix/schemas/semantic_memory.py +162 -0
  46. mirix/schemas/source.py +96 -0
  47. mirix/schemas/step.py +53 -0
  48. mirix/schemas/tool.py +241 -0
  49. mirix/schemas/tool_rule.py +209 -0
  50. mirix/schemas/usage.py +31 -0
  51. mirix/schemas/user.py +67 -0
mirix/schemas/tool.py ADDED
@@ -0,0 +1,241 @@
1
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
2
+
3
+ from pydantic import Field, model_validator
4
+
5
+ from mirix.constants import (
6
+ COMPOSIO_TOOL_TAG_NAME,
7
+ FUNCTION_RETURN_CHAR_LIMIT,
8
+ MIRIX_CORE_TOOL_MODULE_NAME,
9
+ MIRIX_EXTRA_TOOL_MODULE_NAME,
10
+ MIRIX_MEMORY_TOOL_MODULE_NAME,
11
+ )
12
+ from mirix.functions.functions import (
13
+ derive_openai_json_schema,
14
+ get_json_schema_from_module,
15
+ )
16
+ from mirix.functions.helpers import generate_langchain_tool_wrapper
17
+ from mirix.functions.schema_generator import generate_schema_from_args_schema_v2
18
+ from mirix.orm.enums import ToolType
19
+ from mirix.schemas.mirix_base import MirixBase
20
+
21
+ if TYPE_CHECKING:
22
+ try:
23
+ from langchain_core.tools import BaseTool as LangChainBaseTool
24
+ except ImportError:
25
+ LangChainBaseTool = Any # type: ignore
26
+
27
+
28
+ class BaseTool(MirixBase):
29
+ __id_prefix__ = "tool"
30
+
31
+
32
+ class Tool(BaseTool):
33
+ """
34
+ Representation of a tool, which is a function that can be called by the agent.
35
+
36
+ Parameters:
37
+ id (str): The unique identifier of the tool.
38
+ name (str): The name of the function.
39
+ tags (List[str]): Metadata tags.
40
+ source_code (str): The source code of the function.
41
+ json_schema (Dict): The JSON schema of the function.
42
+
43
+ """
44
+
45
+ id: str = BaseTool.generate_id_field()
46
+ tool_type: ToolType = Field(ToolType.CUSTOM, description="The type of the tool.")
47
+ description: Optional[str] = Field(None, description="The description of the tool.")
48
+ source_type: Optional[str] = Field(None, description="The type of the source code.")
49
+ organization_id: Optional[str] = Field(
50
+ None,
51
+ description="The unique identifier of the organization associated with the tool.",
52
+ )
53
+ name: Optional[str] = Field(None, description="The name of the function.")
54
+ tags: List[str] = Field([], description="Metadata tags.")
55
+
56
+ # code
57
+ source_code: Optional[str] = Field(
58
+ None, description="The source code of the function."
59
+ )
60
+ json_schema: Optional[Dict] = Field(
61
+ None, description="The JSON schema of the function."
62
+ )
63
+
64
+ # tool configuration
65
+ return_char_limit: int = Field(
66
+ FUNCTION_RETURN_CHAR_LIMIT,
67
+ description="The maximum number of characters in the response.",
68
+ )
69
+
70
+ # metadata fields
71
+ created_by_id: Optional[str] = Field(
72
+ None, description="The id of the user that made this Tool."
73
+ )
74
+ last_updated_by_id: Optional[str] = Field(
75
+ None, description="The id of the user that made this Tool."
76
+ )
77
+
78
+ @model_validator(mode="after")
79
+ def populate_missing_fields(self):
80
+ """
81
+ Populate missing fields: name, description, and json_schema.
82
+ """
83
+
84
+ if self.tool_type == ToolType.CUSTOM:
85
+ # If it's a custom tool, we need to ensure source_code is present
86
+ if not self.source_code:
87
+ raise ValueError(
88
+ f"Custom tool with id={self.id} is missing source_code field."
89
+ )
90
+
91
+ # Always derive json_schema for freshest possible json_schema
92
+ # TODO: Instead of checking the tag, we should having `COMPOSIO` as a specific ToolType
93
+ # TODO: We skip this for Composio bc composio json schemas are derived differently
94
+ if COMPOSIO_TOOL_TAG_NAME not in self.tags:
95
+ self.json_schema = derive_openai_json_schema(
96
+ source_code=self.source_code
97
+ )
98
+ elif self.tool_type in {ToolType.MIRIX_CORE}:
99
+ # If it's mirix core tool, we generate the json_schema on the fly here
100
+ self.json_schema = get_json_schema_from_module(
101
+ module_name=MIRIX_CORE_TOOL_MODULE_NAME, function_name=self.name
102
+ )
103
+ elif self.tool_type in {ToolType.MIRIX_MEMORY_CORE}:
104
+ self.json_schema = get_json_schema_from_module(
105
+ module_name=MIRIX_MEMORY_TOOL_MODULE_NAME, function_name=self.name
106
+ )
107
+ elif self.tool_type in {ToolType.MIRIX_EXTRA}:
108
+ self.json_schema = get_json_schema_from_module(
109
+ module_name=MIRIX_EXTRA_TOOL_MODULE_NAME, function_name=self.name
110
+ )
111
+ elif self.tool_type in {ToolType.MIRIX_MCP}:
112
+ # MCP tools have their json_schema already provided by MCP tool registry
113
+ # Skip validation since these are auto-generated tools
114
+ if not self.json_schema:
115
+ raise ValueError(f"MCP tool {self.name} is missing json_schema field")
116
+
117
+ # Derive name from the JSON schema if not provided
118
+ if not self.name:
119
+ # TODO: This in theory could error, but name should always be on json_schema
120
+ # TODO: Make JSON schema a typed pydantic object
121
+ self.name = self.json_schema.get("name")
122
+
123
+ # Derive description from the JSON schema if not provided
124
+ if not self.description:
125
+ # TODO: This in theory could error, but description should always be on json_schema
126
+ # TODO: Make JSON schema a typed pydantic object
127
+ self.description = self.json_schema.get("description")
128
+
129
+ return self
130
+
131
+
132
+ class ToolCreate(MirixBase):
133
+ name: Optional[str] = Field(
134
+ None,
135
+ description="The name of the function (auto-generated from source_code if not provided).",
136
+ )
137
+ description: Optional[str] = Field(None, description="The description of the tool.")
138
+ tags: List[str] = Field([], description="Metadata tags.")
139
+ source_code: str = Field(..., description="The source code of the function.")
140
+ source_type: str = Field("python", description="The source type of the function.")
141
+ json_schema: Optional[Dict] = Field(
142
+ None,
143
+ description="The JSON schema of the function (auto-generated from source_code if not provided)",
144
+ )
145
+ return_char_limit: int = Field(
146
+ FUNCTION_RETURN_CHAR_LIMIT,
147
+ description="The maximum number of characters in the response.",
148
+ )
149
+
150
+ @classmethod
151
+ def from_langchain(
152
+ cls,
153
+ langchain_tool: "LangChainBaseTool",
154
+ additional_imports_module_attr_map: dict[str, str] = None,
155
+ ) -> "ToolCreate":
156
+ """
157
+ Class method to create an instance of Tool from a Langchain tool (must be from langchain_community.tools).
158
+
159
+ Args:
160
+ langchain_tool (LangChainBaseTool): An instance of a LangChain BaseTool (BaseTool from LangChain)
161
+ additional_imports_module_attr_map (dict[str, str]): A mapping of module names to attribute name. This is used internally to import all the required classes for the langchain tool. For example, you would pass in `{"langchain_community.utilities": "WikipediaAPIWrapper"}` for `from langchain_community.tools import WikipediaQueryRun`. NOTE: You do NOT need to specify the tool import here, that is done automatically for you.
162
+
163
+ Returns:
164
+ Tool: A Mirix Tool initialized with attributes derived from the provided LangChain BaseTool object.
165
+ """
166
+ description = langchain_tool.description
167
+ source_type = "python"
168
+ tags = ["langchain"]
169
+ # NOTE: langchain tools may come from different packages
170
+ wrapper_func_name, wrapper_function_str = generate_langchain_tool_wrapper(
171
+ langchain_tool, additional_imports_module_attr_map
172
+ )
173
+ json_schema = generate_schema_from_args_schema_v2(
174
+ langchain_tool.args_schema, name=wrapper_func_name, description=description
175
+ )
176
+
177
+ return cls(
178
+ name=wrapper_func_name,
179
+ description=description,
180
+ source_type=source_type,
181
+ tags=tags,
182
+ source_code=wrapper_function_str,
183
+ json_schema=json_schema,
184
+ )
185
+
186
+ @classmethod
187
+ def load_default_langchain_tools(cls) -> List["ToolCreate"]:
188
+ # For now, we only support wikipedia tool
189
+ from langchain_community.tools import WikipediaQueryRun
190
+ from langchain_community.utilities import WikipediaAPIWrapper
191
+
192
+ wikipedia_tool = ToolCreate.from_langchain(
193
+ WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper()),
194
+ {"langchain_community.utilities": "WikipediaAPIWrapper"},
195
+ )
196
+
197
+ return [wikipedia_tool]
198
+
199
+ @classmethod
200
+ def load_default_composio_tools(cls) -> List["ToolCreate"]:
201
+ pass
202
+
203
+ # TODO: Disable composio tools for now
204
+ # TODO: Naming is causing issues
205
+ # calculator = ToolCreate.from_composio(action_name=Action.MATHEMATICAL_CALCULATOR.name)
206
+ # serp_news = ToolCreate.from_composio(action_name=Action.SERPAPI_NEWS_SEARCH.name)
207
+ # serp_google_search = ToolCreate.from_composio(action_name=Action.SERPAPI_SEARCH.name)
208
+ # serp_google_maps = ToolCreate.from_composio(action_name=Action.SERPAPI_GOOGLE_MAPS_SEARCH.name)
209
+
210
+ return []
211
+
212
+
213
+ class ToolUpdate(MirixBase):
214
+ description: Optional[str] = Field(None, description="The description of the tool.")
215
+ name: Optional[str] = Field(None, description="The name of the function.")
216
+ tags: Optional[List[str]] = Field(None, description="Metadata tags.")
217
+ source_code: Optional[str] = Field(
218
+ None, description="The source code of the function."
219
+ )
220
+ source_type: Optional[str] = Field(None, description="The type of the source code.")
221
+ json_schema: Optional[Dict] = Field(
222
+ None,
223
+ description="The JSON schema of the function (auto-generated from source_code if not provided)",
224
+ )
225
+ return_char_limit: Optional[int] = Field(
226
+ None, description="The maximum number of characters in the response."
227
+ )
228
+
229
+ class Config:
230
+ extra = "ignore" # Allows extra fields without validation errors
231
+ # TODO: Remove this, and clean usage of ToolUpdate everywhere else
232
+
233
+
234
+ class ToolRunFromSource(MirixBase):
235
+ source_code: str = Field(..., description="The source code of the function.")
236
+ args: Dict[str, Any] = Field(..., description="The arguments to pass to the tool.")
237
+ env_vars: Dict[str, str] = Field(
238
+ None, description="The environment variables to pass to the tool."
239
+ )
240
+ name: Optional[str] = Field(None, description="The name of the tool to run.")
241
+ source_type: Optional[str] = Field(None, description="The type of the source code.")
@@ -0,0 +1,209 @@
1
+ import json
2
+ from typing import Annotated, Any, Dict, List, Literal, Optional, Set, Union
3
+
4
+ from pydantic import Field
5
+
6
+ from mirix.schemas.enums import ToolRuleType
7
+ from mirix.schemas.mirix_base import MirixBase
8
+
9
+
10
+ class BaseToolRule(MirixBase):
11
+ __id_prefix__ = "tool_rule"
12
+ tool_name: str = Field(
13
+ ...,
14
+ description="The name of the tool. Must exist in the database for the user's organization.",
15
+ )
16
+ type: ToolRuleType = Field(..., description="The type of the message.")
17
+
18
+ def get_valid_tools(
19
+ self,
20
+ tool_call_history: List[str],
21
+ available_tools: Set[str],
22
+ last_function_response: Optional[str],
23
+ ) -> set[str]:
24
+ raise NotImplementedError
25
+
26
+
27
+ class ChildToolRule(BaseToolRule):
28
+ """
29
+ A ToolRule represents a tool that can be invoked by the agent.
30
+ """
31
+
32
+ type: Literal[ToolRuleType.constrain_child_tools] = (
33
+ ToolRuleType.constrain_child_tools
34
+ )
35
+ children: List[str] = Field(
36
+ ..., description="The children tools that can be invoked."
37
+ )
38
+
39
+ def get_valid_tools(
40
+ self,
41
+ tool_call_history: List[str],
42
+ available_tools: Set[str],
43
+ last_function_response: Optional[str],
44
+ ) -> Set[str]:
45
+ last_tool = tool_call_history[-1] if tool_call_history else None
46
+ return set(self.children) if last_tool == self.tool_name else available_tools
47
+
48
+
49
+ class ParentToolRule(BaseToolRule):
50
+ """
51
+ A ToolRule that only allows a child tool to be called if the parent has been called.
52
+ """
53
+
54
+ type: Literal[ToolRuleType.parent_last_tool] = ToolRuleType.parent_last_tool
55
+ children: List[str] = Field(
56
+ ..., description="The children tools that can be invoked."
57
+ )
58
+
59
+ def get_valid_tools(
60
+ self,
61
+ tool_call_history: List[str],
62
+ available_tools: Set[str],
63
+ last_function_response: Optional[str],
64
+ ) -> Set[str]:
65
+ last_tool = tool_call_history[-1] if tool_call_history else None
66
+ return (
67
+ set(self.children)
68
+ if last_tool == self.tool_name
69
+ else available_tools - set(self.children)
70
+ )
71
+
72
+
73
+ class ConditionalToolRule(BaseToolRule):
74
+ """
75
+ A ToolRule that conditionally maps to different child tools based on the output.
76
+ """
77
+
78
+ type: Literal[ToolRuleType.conditional] = ToolRuleType.conditional
79
+ default_child: Optional[str] = Field(
80
+ None,
81
+ description="The default child tool to be called. If None, any tool can be called.",
82
+ )
83
+ child_output_mapping: Dict[Any, str] = Field(
84
+ ..., description="The output case to check for mapping"
85
+ )
86
+ require_output_mapping: bool = Field(
87
+ default=False,
88
+ description="Whether to throw an error when output doesn't match any case",
89
+ )
90
+
91
+ def get_valid_tools(
92
+ self,
93
+ tool_call_history: List[str],
94
+ available_tools: Set[str],
95
+ last_function_response: Optional[str],
96
+ ) -> Set[str]:
97
+ """Determine valid tools based on function output mapping."""
98
+ if not tool_call_history or tool_call_history[-1] != self.tool_name:
99
+ return available_tools # No constraints if this rule doesn't apply
100
+
101
+ if not last_function_response:
102
+ raise ValueError(
103
+ "Conditional tool rule requires an LLM response to determine which child tool to use"
104
+ )
105
+
106
+ try:
107
+ json_response = json.loads(last_function_response)
108
+ function_output = json_response.get("message", "")
109
+ except json.JSONDecodeError:
110
+ if self.require_output_mapping:
111
+ return set() # Strict mode: Invalid response means no allowed tools
112
+ return {self.default_child} if self.default_child else available_tools
113
+
114
+ # Match function output to a mapped child tool
115
+ for key, tool in self.child_output_mapping.items():
116
+ if self._matches_key(function_output, key):
117
+ return {tool}
118
+
119
+ # If no match found, use default or allow all tools if no default is set
120
+ if self.require_output_mapping:
121
+ return set() # Strict mode: No match means no valid tools
122
+
123
+ return {self.default_child} if self.default_child else available_tools
124
+
125
+ def _matches_key(self, function_output: str, key: Any) -> bool:
126
+ """Helper function to determine if function output matches a mapping key."""
127
+ if isinstance(key, bool):
128
+ return (
129
+ function_output.lower() == "true"
130
+ if key
131
+ else function_output.lower() == "false"
132
+ )
133
+ elif isinstance(key, int):
134
+ try:
135
+ return int(function_output) == key
136
+ except ValueError:
137
+ return False
138
+ elif isinstance(key, float):
139
+ try:
140
+ return float(function_output) == key
141
+ except ValueError:
142
+ return False
143
+ else: # Assume string
144
+ return str(function_output) == str(key)
145
+
146
+
147
+ class InitToolRule(BaseToolRule):
148
+ """
149
+ Represents the initial tool rule configuration.
150
+ """
151
+
152
+ type: Literal[ToolRuleType.run_first] = ToolRuleType.run_first
153
+
154
+
155
+ class TerminalToolRule(BaseToolRule):
156
+ """
157
+ Represents a terminal tool rule configuration where if this tool gets called, it must end the agent loop.
158
+ """
159
+
160
+ type: Literal[ToolRuleType.exit_loop] = ToolRuleType.exit_loop
161
+
162
+
163
+ class ContinueToolRule(BaseToolRule):
164
+ """
165
+ Represents a tool rule configuration where if this tool gets called, it must continue the agent loop.
166
+ """
167
+
168
+ type: Literal[ToolRuleType.continue_loop] = ToolRuleType.continue_loop
169
+
170
+
171
+ class MaxCountPerStepToolRule(BaseToolRule):
172
+ """
173
+ Represents a tool rule configuration which constrains the total number of times this tool can be invoked in a single step.
174
+ """
175
+
176
+ type: Literal[ToolRuleType.max_count_per_step] = ToolRuleType.max_count_per_step
177
+ max_count_limit: int = Field(
178
+ ...,
179
+ description="The max limit for the total number of times this tool can be invoked in a single step.",
180
+ )
181
+
182
+ def get_valid_tools(
183
+ self,
184
+ tool_call_history: List[str],
185
+ available_tools: Set[str],
186
+ last_function_response: Optional[str],
187
+ ) -> Set[str]:
188
+ """Restricts the tool if it has been called max_count_limit times in the current step."""
189
+ count = tool_call_history.count(self.tool_name)
190
+
191
+ # If the tool has been used max_count_limit times, it is no longer allowed
192
+ if count >= self.max_count_limit:
193
+ return available_tools - {self.tool_name}
194
+
195
+ return available_tools
196
+
197
+
198
+ ToolRule = Annotated[
199
+ Union[
200
+ ChildToolRule,
201
+ InitToolRule,
202
+ TerminalToolRule,
203
+ ConditionalToolRule,
204
+ ContinueToolRule,
205
+ MaxCountPerStepToolRule,
206
+ ParentToolRule,
207
+ ],
208
+ Field(discriminator="type"),
209
+ ]
mirix/schemas/usage.py ADDED
@@ -0,0 +1,31 @@
1
+ from typing import Literal
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class MirixUsageStatistics(BaseModel):
7
+ """
8
+ Usage statistics for the agent interaction.
9
+
10
+ Attributes:
11
+ completion_tokens (int): The number of tokens generated by the agent.
12
+ prompt_tokens (int): The number of tokens in the prompt.
13
+ total_tokens (int): The total number of tokens processed by the agent.
14
+ step_count (int): The number of steps taken by the agent.
15
+ """
16
+
17
+ message_type: Literal["usage_statistics"] = "usage_statistics"
18
+ completion_tokens: int = Field(
19
+ 0, description="The number of tokens generated by the agent."
20
+ )
21
+ prompt_tokens: int = Field(0, description="The number of tokens in the prompt.")
22
+ last_prompt_tokens: int = Field(
23
+ 0, description="The number of tokens in the last prompt."
24
+ )
25
+ last_completion_tokens: int = Field(
26
+ 0, description="The number of tokens in the last completion."
27
+ )
28
+ total_tokens: int = Field(
29
+ 0, description="The total number of tokens processed by the agent."
30
+ )
31
+ step_count: int = Field(0, description="The number of steps taken by the agent.")
mirix/schemas/user.py ADDED
@@ -0,0 +1,67 @@
1
+ from datetime import datetime
2
+ from typing import Optional
3
+ import uuid
4
+
5
+ from pydantic import Field
6
+
7
+ from mirix.helpers.datetime_helpers import get_utc_time
8
+ from mirix.schemas.mirix_base import MirixBase
9
+ from mirix.services.organization_manager import OrganizationManager
10
+
11
+
12
+ class UserBase(MirixBase):
13
+ __id_prefix__ = "user"
14
+
15
+
16
+ def _generate_user_id() -> str:
17
+ """Generate a random user ID."""
18
+ return f"user-{uuid.uuid4().hex[:8]}"
19
+
20
+
21
+ class User(UserBase):
22
+ """
23
+ Representation of a user.
24
+
25
+ Parameters:
26
+ id (str): The unique identifier of the user.
27
+ name (str): The name of the user.
28
+ status (str): Whether the user is active or not.
29
+ created_at (datetime): The creation date of the user.
30
+ """
31
+
32
+ id: str = Field(
33
+ default_factory=_generate_user_id,
34
+ description="The unique identifier of the user.",
35
+ )
36
+ organization_id: Optional[str] = Field(
37
+ OrganizationManager.DEFAULT_ORG_ID,
38
+ description="The organization id of the user",
39
+ )
40
+ name: str = Field(..., description="The name of the user.")
41
+ status: str = Field("active", description="Whether the user is active or not.")
42
+ timezone: str = Field(..., description="The timezone of the user.")
43
+ created_at: Optional[datetime] = Field(
44
+ default_factory=get_utc_time, description="The creation date of the user."
45
+ )
46
+ updated_at: Optional[datetime] = Field(
47
+ default_factory=get_utc_time, description="The update date of the user."
48
+ )
49
+ is_deleted: bool = Field(False, description="Whether this user is deleted or not.")
50
+
51
+
52
+ class UserCreate(UserBase):
53
+ id: Optional[str] = Field(None, description="The unique identifier of the user.")
54
+ name: str = Field(..., description="The name of the user.")
55
+ status: str = Field("active", description="Whether the user is active or not.")
56
+ timezone: str = Field(..., description="The timezone of the user.")
57
+ organization_id: str = Field(..., description="The organization id of the user.")
58
+
59
+
60
+ class UserUpdate(UserBase):
61
+ id: str = Field(..., description="The id of the user to update.")
62
+ name: Optional[str] = Field(None, description="The new name of the user.")
63
+ status: Optional[str] = Field(None, description="The new status of the user.")
64
+ timezone: Optional[str] = Field(None, description="The new timezone of the user.")
65
+ organization_id: Optional[str] = Field(
66
+ None, description="The new organization id of the user."
67
+ )