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/client/client.py ADDED
@@ -0,0 +1,2594 @@
1
+ import base64
2
+ import hashlib
3
+ import logging
4
+ import os
5
+ import shutil
6
+ from pathlib import Path
7
+ from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union
8
+ from urllib.parse import urlparse
9
+
10
+ import requests
11
+
12
+ import mirix.utils
13
+
14
+ if TYPE_CHECKING:
15
+ try:
16
+ from composio import ActionType
17
+ except ImportError:
18
+ ActionType = Any # type: ignore
19
+ try:
20
+ from crewai_tools import BaseTool as CrewAIBaseTool
21
+ except ImportError:
22
+ CrewAIBaseTool = Any # type: ignore
23
+ try:
24
+ from langchain_core.tools import BaseTool as LangChainBaseTool
25
+ except ImportError:
26
+ LangChainBaseTool = Any # type: ignore
27
+ from mirix.constants import (
28
+ BASE_TOOLS,
29
+ DEFAULT_HUMAN,
30
+ DEFAULT_PERSONA,
31
+ FUNCTION_RETURN_CHAR_LIMIT,
32
+ META_MEMORY_TOOLS,
33
+ )
34
+ from mirix.functions.functions import parse_source_code
35
+ from mirix.interface import QueuingInterface
36
+ from mirix.orm.errors import NoResultFound
37
+ from mirix.schemas.agent import (
38
+ AgentState,
39
+ AgentType,
40
+ CreateAgent,
41
+ CreateMetaAgent,
42
+ )
43
+ from mirix.schemas.block import Block, BlockUpdate, CreateBlock, Human, Persona
44
+ from mirix.schemas.embedding_config import EmbeddingConfig
45
+
46
+ # new schemas
47
+ from mirix.schemas.enums import MessageRole
48
+ from mirix.schemas.environment_variables import (
49
+ SandboxEnvironmentVariable,
50
+ SandboxEnvironmentVariableCreate,
51
+ SandboxEnvironmentVariableUpdate,
52
+ )
53
+ from mirix.schemas.file import FileMetadata
54
+ from mirix.schemas.file import FileMetadata as PydanticFileMetadata
55
+ from mirix.schemas.llm_config import LLMConfig
56
+ from mirix.schemas.memory import (
57
+ ArchivalMemorySummary,
58
+ Memory,
59
+ RecallMemorySummary,
60
+ )
61
+ from mirix.schemas.message import Message, MessageCreate
62
+ from mirix.schemas.mirix_message_content import (
63
+ CloudFileContent,
64
+ FileContent,
65
+ ImageContent,
66
+ MessageContentType,
67
+ TextContent,
68
+ )
69
+ from mirix.schemas.mirix_response import MirixResponse
70
+ from mirix.schemas.organization import Organization
71
+ from mirix.schemas.sandbox_config import (
72
+ E2BSandboxConfig,
73
+ LocalSandboxConfig,
74
+ SandboxConfig,
75
+ SandboxConfigCreate,
76
+ SandboxConfigUpdate,
77
+ )
78
+ from mirix.schemas.tool import Tool, ToolCreate, ToolUpdate
79
+ from mirix.schemas.tool_rule import BaseToolRule
80
+ from mirix.schemas.user import User as PydanticUser, UserCreate
81
+
82
+
83
+ def create_client():
84
+ return LocalClient()
85
+
86
+
87
+ class AbstractClient(object):
88
+ def __init__(
89
+ self,
90
+ debug: bool = False,
91
+ ):
92
+ self.debug = debug
93
+
94
+ def agent_exists(
95
+ self, agent_id: Optional[str] = None, agent_name: Optional[str] = None
96
+ ) -> bool:
97
+ raise NotImplementedError
98
+
99
+ def create_agent(
100
+ self,
101
+ name: Optional[str] = None,
102
+ agent_type: Optional[AgentType] = AgentType.chat_agent,
103
+ embedding_config: Optional[EmbeddingConfig] = None,
104
+ llm_config: Optional[LLMConfig] = None,
105
+ memory: Optional[Memory] = None,
106
+ block_ids: Optional[List[str]] = None,
107
+ system: Optional[str] = None,
108
+ tool_ids: Optional[List[str]] = None,
109
+ tool_rules: Optional[List[BaseToolRule]] = None,
110
+ include_base_tools: Optional[bool] = True,
111
+ include_meta_memory_tools: Optional[bool] = False,
112
+ metadata: Optional[Dict] = {
113
+ "human:": DEFAULT_HUMAN,
114
+ "persona": DEFAULT_PERSONA,
115
+ },
116
+ description: Optional[str] = None,
117
+ initial_message_sequence: Optional[List[Message]] = None,
118
+ tags: Optional[List[str]] = None,
119
+ ) -> AgentState:
120
+ raise NotImplementedError
121
+
122
+ def update_agent(
123
+ self,
124
+ agent_id: str,
125
+ name: Optional[str] = None,
126
+ description: Optional[str] = None,
127
+ system: Optional[str] = None,
128
+ tool_ids: Optional[List[str]] = None,
129
+ metadata: Optional[Dict] = None,
130
+ llm_config: Optional[LLMConfig] = None,
131
+ embedding_config: Optional[EmbeddingConfig] = None,
132
+ message_ids: Optional[List[str]] = None,
133
+ memory: Optional[Memory] = None,
134
+ tags: Optional[List[str]] = None,
135
+ ):
136
+ raise NotImplementedError
137
+
138
+ def get_tools_from_agent(self, agent_id: str):
139
+ raise NotImplementedError
140
+
141
+ def add_tool_to_agent(self, agent_id: str, tool_id: str):
142
+ raise NotImplementedError
143
+
144
+ def remove_tool_from_agent(self, agent_id: str, tool_id: str):
145
+ raise NotImplementedError
146
+
147
+ def rename_agent(self, agent_id: str, new_name: str):
148
+ raise NotImplementedError
149
+
150
+ def delete_agent(self, agent_id: str):
151
+ raise NotImplementedError
152
+
153
+ def get_agent(self, agent_id: str) -> AgentState:
154
+ raise NotImplementedError
155
+
156
+ def get_agent_id(self, agent_name: str) -> AgentState:
157
+ raise NotImplementedError
158
+
159
+ def get_in_context_memory(self, agent_id: str) -> Memory:
160
+ raise NotImplementedError
161
+
162
+ def update_in_context_memory(
163
+ self, agent_id: str, section: str, value: Union[List[str], str]
164
+ ) -> Memory:
165
+ raise NotImplementedError
166
+
167
+ def get_archival_memory_summary(self, agent_id: str) -> ArchivalMemorySummary:
168
+ raise NotImplementedError
169
+
170
+ def get_recall_memory_summary(self, agent_id: str) -> RecallMemorySummary:
171
+ raise NotImplementedError
172
+
173
+ def get_in_context_messages(self, agent_id: str) -> List[Message]:
174
+ raise NotImplementedError
175
+
176
+ def send_message(
177
+ self,
178
+ message: str,
179
+ role: str,
180
+ agent_id: Optional[str] = None,
181
+ agent_name: Optional[str] = None,
182
+ name: Optional[str] = None,
183
+ stream_steps: bool = False,
184
+ stream_tokens: bool = False,
185
+ chaining: Optional[bool] = None,
186
+ verbose: Optional[bool] = None,
187
+ ) -> MirixResponse:
188
+ raise NotImplementedError
189
+
190
+ def user_message(self, agent_id: str, message: str) -> MirixResponse:
191
+ raise NotImplementedError
192
+
193
+ def create_human(self, name: str, text: str) -> Human:
194
+ raise NotImplementedError
195
+
196
+ def create_persona(self, name: str, text: str) -> Persona:
197
+ raise NotImplementedError
198
+
199
+ def list_humans(self) -> List[Human]:
200
+ raise NotImplementedError
201
+
202
+ def list_personas(self) -> List[Persona]:
203
+ raise NotImplementedError
204
+
205
+ def update_human(self, human_id: str, text: str) -> Human:
206
+ raise NotImplementedError
207
+
208
+ def update_persona(self, persona_id: str, text: str) -> Persona:
209
+ raise NotImplementedError
210
+
211
+ def get_persona(self, id: str) -> Persona:
212
+ raise NotImplementedError
213
+
214
+ def get_human(self, id: str) -> Human:
215
+ raise NotImplementedError
216
+
217
+ def get_persona_id(self, name: str) -> str:
218
+ raise NotImplementedError
219
+
220
+ def get_human_id(self, name: str) -> str:
221
+ raise NotImplementedError
222
+
223
+ def delete_persona(self, id: str):
224
+ raise NotImplementedError
225
+
226
+ def delete_human(self, id: str):
227
+ raise NotImplementedError
228
+
229
+ def load_langchain_tool(
230
+ self,
231
+ langchain_tool: "LangChainBaseTool",
232
+ additional_imports_module_attr_map: dict[str, str] = None,
233
+ ) -> Tool:
234
+ raise NotImplementedError
235
+
236
+ def load_composio_tool(self, action: "ActionType") -> Tool:
237
+ raise NotImplementedError
238
+
239
+ def create_tool(
240
+ self,
241
+ func,
242
+ name: Optional[str] = None,
243
+ tags: Optional[List[str]] = None,
244
+ return_char_limit: int = FUNCTION_RETURN_CHAR_LIMIT,
245
+ ) -> Tool:
246
+ raise NotImplementedError
247
+
248
+ def create_or_update_tool(
249
+ self,
250
+ func,
251
+ name: Optional[str] = None,
252
+ tags: Optional[List[str]] = None,
253
+ return_char_limit: int = FUNCTION_RETURN_CHAR_LIMIT,
254
+ ) -> Tool:
255
+ raise NotImplementedError
256
+
257
+ def update_tool(
258
+ self,
259
+ id: str,
260
+ name: Optional[str] = None,
261
+ description: Optional[str] = None,
262
+ func: Optional[Callable] = None,
263
+ tags: Optional[List[str]] = None,
264
+ return_char_limit: int = FUNCTION_RETURN_CHAR_LIMIT,
265
+ ) -> Tool:
266
+ raise NotImplementedError
267
+
268
+ def list_tools(
269
+ self, cursor: Optional[str] = None, limit: Optional[int] = 50
270
+ ) -> List[Tool]:
271
+ raise NotImplementedError
272
+
273
+ def get_tool(self, id: str) -> Tool:
274
+ raise NotImplementedError
275
+
276
+ def delete_tool(self, id: str):
277
+ raise NotImplementedError
278
+
279
+ def get_tool_id(self, name: str) -> Optional[str]:
280
+ raise NotImplementedError
281
+
282
+ def upsert_base_tools(self) -> List[Tool]:
283
+ raise NotImplementedError
284
+
285
+ def get_messages(
286
+ self,
287
+ agent_id: str,
288
+ before: Optional[str] = None,
289
+ after: Optional[str] = None,
290
+ limit: Optional[int] = 1000,
291
+ ) -> List[Message]:
292
+ raise NotImplementedError
293
+
294
+ def list_model_configs(self) -> List[LLMConfig]:
295
+ raise NotImplementedError
296
+
297
+ def list_embedding_configs(self) -> List[EmbeddingConfig]:
298
+ raise NotImplementedError
299
+
300
+ def create_org(self, name: Optional[str] = None) -> Organization:
301
+ raise NotImplementedError
302
+
303
+ def list_orgs(
304
+ self, cursor: Optional[str] = None, limit: Optional[int] = 50
305
+ ) -> List[Organization]:
306
+ raise NotImplementedError
307
+
308
+ def delete_org(self, org_id: str) -> Organization:
309
+ raise NotImplementedError
310
+
311
+ def create_sandbox_config(
312
+ self, config: Union[LocalSandboxConfig, E2BSandboxConfig]
313
+ ) -> SandboxConfig:
314
+ """
315
+ Create a new sandbox configuration.
316
+
317
+ Args:
318
+ config (Union[LocalSandboxConfig, E2BSandboxConfig]): The sandbox settings.
319
+
320
+ Returns:
321
+ SandboxConfig: The created sandbox configuration.
322
+ """
323
+ raise NotImplementedError
324
+
325
+ def update_sandbox_config(
326
+ self,
327
+ sandbox_config_id: str,
328
+ config: Union[LocalSandboxConfig, E2BSandboxConfig],
329
+ ) -> SandboxConfig:
330
+ """
331
+ Update an existing sandbox configuration.
332
+
333
+ Args:
334
+ sandbox_config_id (str): The ID of the sandbox configuration to update.
335
+ config (Union[LocalSandboxConfig, E2BSandboxConfig]): The updated sandbox settings.
336
+
337
+ Returns:
338
+ SandboxConfig: The updated sandbox configuration.
339
+ """
340
+ raise NotImplementedError
341
+
342
+ def delete_sandbox_config(self, sandbox_config_id: str) -> None:
343
+ """
344
+ Delete a sandbox configuration.
345
+
346
+ Args:
347
+ sandbox_config_id (str): The ID of the sandbox configuration to delete.
348
+ """
349
+ raise NotImplementedError
350
+
351
+ def list_sandbox_configs(
352
+ self, limit: int = 50, cursor: Optional[str] = None
353
+ ) -> List[SandboxConfig]:
354
+ """
355
+ List all sandbox configurations.
356
+
357
+ Args:
358
+ limit (int, optional): The maximum number of sandbox configurations to return. Defaults to 50.
359
+ cursor (Optional[str], optional): The pagination cursor for retrieving the next set of results.
360
+
361
+ Returns:
362
+ List[SandboxConfig]: A list of sandbox configurations.
363
+ """
364
+ raise NotImplementedError
365
+
366
+ def create_sandbox_env_var(
367
+ self,
368
+ sandbox_config_id: str,
369
+ key: str,
370
+ value: str,
371
+ description: Optional[str] = None,
372
+ ) -> SandboxEnvironmentVariable:
373
+ """
374
+ Create a new environment variable for a sandbox configuration.
375
+
376
+ Args:
377
+ sandbox_config_id (str): The ID of the sandbox configuration to associate the environment variable with.
378
+ key (str): The name of the environment variable.
379
+ value (str): The value of the environment variable.
380
+ description (Optional[str], optional): A description of the environment variable. Defaults to None.
381
+
382
+ Returns:
383
+ SandboxEnvironmentVariable: The created environment variable.
384
+ """
385
+ raise NotImplementedError
386
+
387
+ def update_sandbox_env_var(
388
+ self,
389
+ env_var_id: str,
390
+ key: Optional[str] = None,
391
+ value: Optional[str] = None,
392
+ description: Optional[str] = None,
393
+ ) -> SandboxEnvironmentVariable:
394
+ """
395
+ Update an existing environment variable.
396
+
397
+ Args:
398
+ env_var_id (str): The ID of the environment variable to update.
399
+ key (Optional[str], optional): The updated name of the environment variable. Defaults to None.
400
+ value (Optional[str], optional): The updated value of the environment variable. Defaults to None.
401
+ description (Optional[str], optional): The updated description of the environment variable. Defaults to None.
402
+
403
+ Returns:
404
+ SandboxEnvironmentVariable: The updated environment variable.
405
+ """
406
+ raise NotImplementedError
407
+
408
+ def delete_sandbox_env_var(self, env_var_id: str) -> None:
409
+ """
410
+ Delete an environment variable by its ID.
411
+
412
+ Args:
413
+ env_var_id (str): The ID of the environment variable to delete.
414
+ """
415
+ raise NotImplementedError
416
+
417
+ def list_sandbox_env_vars(
418
+ self, sandbox_config_id: str, limit: int = 50, cursor: Optional[str] = None
419
+ ) -> List[SandboxEnvironmentVariable]:
420
+ """
421
+ List all environment variables associated with a sandbox configuration.
422
+
423
+ Args:
424
+ sandbox_config_id (str): The ID of the sandbox configuration to retrieve environment variables for.
425
+ limit (int, optional): The maximum number of environment variables to return. Defaults to 50.
426
+ cursor (Optional[str], optional): The pagination cursor for retrieving the next set of results.
427
+
428
+ Returns:
429
+ List[SandboxEnvironmentVariable]: A list of environment variables.
430
+ """
431
+ raise NotImplementedError
432
+
433
+
434
+ class LocalClient(AbstractClient):
435
+ """
436
+ A local client for Mirix, which corresponds to a single user.
437
+
438
+ Attributes:
439
+ user_id (str): The user ID.
440
+ debug (bool): Whether to print debug information.
441
+ interface (QueuingInterface): The interface for the client.
442
+ server (SyncServer): The server for the client.
443
+ """
444
+
445
+ def __init__(
446
+ self,
447
+ user_id: Optional[str] = None,
448
+ org_id: Optional[str] = None,
449
+ debug: bool = False,
450
+ default_llm_config: Optional[LLMConfig] = None,
451
+ default_embedding_config: Optional[EmbeddingConfig] = None,
452
+ ):
453
+ """
454
+ Initializes a new instance of Client class.
455
+
456
+ Args:
457
+ user_id (str): The user ID.
458
+ debug (bool): Whether to print debug information.
459
+ """
460
+
461
+ from mirix.server.server import SyncServer
462
+
463
+ # set logging levels
464
+ mirix.utils.DEBUG = debug
465
+ logging.getLogger().setLevel(logging.CRITICAL)
466
+
467
+ # save default model config
468
+ self._default_llm_config = default_llm_config
469
+ self._default_embedding_config = default_embedding_config
470
+
471
+ # create server
472
+ self.interface = QueuingInterface(debug=debug)
473
+ self.server = SyncServer(default_interface_factory=lambda: self.interface)
474
+
475
+ # initialize file manager
476
+ from mirix.services.file_manager import FileManager
477
+
478
+ self.file_manager = FileManager()
479
+
480
+ # save org_id that `LocalClient` is associated with
481
+ if org_id:
482
+ self.org_id = org_id
483
+ else:
484
+ self.org_id = self.server.organization_manager.DEFAULT_ORG_ID
485
+ # save user_id that `LocalClient` is associated with
486
+ if user_id:
487
+ self.user_id = user_id
488
+ else:
489
+ # get default user
490
+ self.user_id = self.server.user_manager.DEFAULT_USER_ID
491
+
492
+ self.user = self.server.user_manager.get_user_or_default(self.user_id)
493
+ self.organization = self.server.get_organization_or_default(self.org_id)
494
+
495
+ # get images directory from settings and ensure it exists
496
+ # Can be customized via MIRIX_IMAGES_DIR environment variable
497
+ from mirix.settings import settings
498
+
499
+ self.images_dir = Path(settings.images_dir)
500
+ self.images_dir.mkdir(parents=True, exist_ok=True)
501
+
502
+ def _generate_file_hash(self, content: bytes) -> str:
503
+ """Generate a unique hash for file content to avoid duplicates."""
504
+ return hashlib.sha256(content).hexdigest()[:16]
505
+
506
+ def _save_image_from_base64(
507
+ self, base64_data: str, detail: str = "auto"
508
+ ) -> FileMetadata:
509
+ """Save an image from base64 data and return FileMetadata."""
510
+ try:
511
+ # Parse the data URL format: data:image/jpeg;base64,{data}
512
+ if base64_data.startswith("data:"):
513
+ header, encoded = base64_data.split(",", 1)
514
+ # Extract MIME type from header
515
+ mime_type = header.split(":")[1].split(";")[0]
516
+ file_extension = mime_type.split("/")[-1]
517
+ else:
518
+ # Assume it's just base64 data without header
519
+ encoded = base64_data
520
+ mime_type = "image/jpeg"
521
+ file_extension = "jpg"
522
+
523
+ # Decode base64 data
524
+ image_data = base64.b64decode(encoded)
525
+
526
+ # Generate unique filename using hash
527
+ file_hash = self._generate_file_hash(image_data)
528
+ file_name = f"image_{file_hash}.{file_extension}"
529
+ file_path = self.images_dir / file_name
530
+
531
+ # Check if file already exists
532
+ if not file_path.exists():
533
+ # Save the image data
534
+ with open(file_path, "wb") as f:
535
+ f.write(image_data)
536
+
537
+ # Create FileMetadata
538
+ file_metadata = self.file_manager.create_file_metadata_from_path(
539
+ file_path=str(file_path), organization_id=self.org_id
540
+ )
541
+
542
+ return file_metadata
543
+
544
+ except Exception as e:
545
+ raise ValueError(f"Failed to save base64 image: {str(e)}")
546
+
547
+ def _save_image_from_url(self, url: str, detail: str = "auto") -> FileMetadata:
548
+ """Download and save an image from URL and return FileMetadata."""
549
+ try:
550
+ # Download the image
551
+ response = requests.get(url, stream=True, timeout=30)
552
+ response.raise_for_status()
553
+
554
+ # Get content type and determine file extension
555
+ content_type = response.headers.get("content-type", "image/jpeg")
556
+ file_extension = content_type.split("/")[-1]
557
+ if file_extension not in ["jpg", "jpeg", "png", "gif", "webp"]:
558
+ file_extension = "jpg"
559
+
560
+ # Get the image content
561
+ image_data = response.content
562
+
563
+ # Generate unique filename using hash
564
+ file_hash = self._generate_file_hash(image_data)
565
+ file_name = f"image_{file_hash}.{file_extension}"
566
+ file_path = self.images_dir / file_name
567
+
568
+ # Check if file already exists
569
+ if not file_path.exists():
570
+ # Save the image data
571
+ with open(file_path, "wb") as f:
572
+ f.write(image_data)
573
+
574
+ # Create FileMetadata
575
+ file_metadata = self.file_manager.create_file_metadata_from_path(
576
+ file_path=str(file_path), organization_id=self.org_id
577
+ )
578
+
579
+ return file_metadata
580
+
581
+ except Exception as e:
582
+ raise ValueError(
583
+ f"Failed to download and save image from URL {url}: {str(e)}"
584
+ )
585
+
586
+ def _save_image_from_file_uri(self, file_uri: str) -> FileMetadata:
587
+ """Copy an image from file URI and return FileMetadata."""
588
+ try:
589
+ # Parse file URI (could be file:// or just a local path)
590
+ if file_uri.startswith("file://"):
591
+ source_path = file_uri[7:] # Remove 'file://' prefix
592
+ else:
593
+ source_path = file_uri
594
+
595
+ source_path = Path(source_path)
596
+
597
+ if not source_path.exists():
598
+ raise FileNotFoundError(f"Source file not found: {source_path}")
599
+
600
+ # Read the file content
601
+ with open(source_path, "rb") as f:
602
+ image_data = f.read()
603
+
604
+ # Generate unique filename using hash
605
+ file_hash = self._generate_file_hash(image_data)
606
+ file_extension = source_path.suffix.lstrip(".") or "jpg"
607
+ file_name = f"image_{file_hash}.{file_extension}"
608
+ file_path = self.images_dir / file_name
609
+
610
+ # Check if file already exists
611
+ if not file_path.exists():
612
+ # Copy the file
613
+ shutil.copy2(source_path, file_path)
614
+
615
+ # Create FileMetadata
616
+ file_metadata = self.file_manager.create_file_metadata_from_path(
617
+ file_path=str(file_path), organization_id=self.org_id
618
+ )
619
+
620
+ return file_metadata
621
+
622
+ except Exception as e:
623
+ raise ValueError(f"Failed to copy image from file URI {file_uri}: {str(e)}")
624
+
625
+ def _save_image_from_google_cloud_uri(self, cloud_uri: str) -> FileMetadata:
626
+ """Create FileMetadata from Google Cloud URI without downloading the image.
627
+
628
+ Google Cloud URIs are not directly downloadable and should be stored as remote references
629
+ in the source_url field, similar to how regular HTTP URLs are handled.
630
+ """
631
+ # Parse URI to get file name - Google Cloud URIs typically come in the format:
632
+ # https://generativelanguage.googleapis.com/v1beta/files/{file_id}
633
+
634
+ parsed_uri = urlparse(cloud_uri)
635
+
636
+ # Extract file ID from path if available, otherwise use generic name
637
+ file_id = os.path.basename(parsed_uri.path) or "google_cloud_file"
638
+ file_name = f"google_cloud_{file_id}"
639
+
640
+ # Ensure file name has an extension
641
+ if not os.path.splitext(file_name)[1]:
642
+ file_name += ".jpg" # Default to jpg for images without extension
643
+
644
+ # Determine MIME type from extension or default to image/jpeg
645
+ file_extension = os.path.splitext(file_name)[1].lower()
646
+ file_type_map = {
647
+ ".jpg": "image/jpeg",
648
+ ".jpeg": "image/jpeg",
649
+ ".png": "image/png",
650
+ ".gif": "image/gif",
651
+ ".webp": "image/webp",
652
+ ".bmp": "image/bmp",
653
+ ".svg": "image/svg+xml",
654
+ }
655
+ file_type = file_type_map.get(file_extension, "image/jpeg")
656
+
657
+ # Create FileMetadata with Google Cloud URI in google_cloud_url field
658
+ file_metadata = self.file_manager.create_file_metadata(
659
+ PydanticFileMetadata(
660
+ organization_id=self.org_id,
661
+ file_name=file_name,
662
+ file_path=None, # No local path for Google Cloud URIs
663
+ source_url=None, # No regular source URL for Google Cloud files
664
+ google_cloud_url=cloud_uri, # Store Google Cloud URI in the dedicated field
665
+ file_type=file_type,
666
+ file_size=None, # Unknown size for remote Google Cloud files
667
+ file_creation_date=None,
668
+ file_last_modified_date=None,
669
+ )
670
+ )
671
+
672
+ return file_metadata
673
+
674
+ def _save_file_from_path(self, file_path: str) -> FileMetadata:
675
+ """Save a file from local path and return FileMetadata."""
676
+ try:
677
+ file_path = Path(file_path)
678
+
679
+ if not file_path.exists():
680
+ raise FileNotFoundError(f"File not found: {file_path}")
681
+
682
+ # Create FileMetadata using the file manager
683
+ file_metadata = self.file_manager.create_file_metadata_from_path(
684
+ file_path=str(file_path), organization_id=self.org_id
685
+ )
686
+
687
+ return file_metadata
688
+
689
+ except Exception as e:
690
+ raise ValueError(f"Failed to save file from path {file_path}: {str(e)}")
691
+
692
+ def _determine_file_type(self, file_path: str) -> str:
693
+ """Determine file type from file extension."""
694
+ file_extension = os.path.splitext(file_path)[1].lower()
695
+ file_type_map = {
696
+ # Images
697
+ ".jpg": "image/jpeg",
698
+ ".jpeg": "image/jpeg",
699
+ ".png": "image/png",
700
+ ".gif": "image/gif",
701
+ ".webp": "image/webp",
702
+ ".bmp": "image/bmp",
703
+ ".svg": "image/svg+xml",
704
+ # Documents
705
+ ".pdf": "application/pdf",
706
+ ".doc": "application/msword",
707
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
708
+ ".txt": "text/plain",
709
+ ".rtf": "application/rtf",
710
+ ".html": "text/html",
711
+ ".htm": "text/html",
712
+ # Spreadsheets
713
+ ".xls": "application/vnd.ms-excel",
714
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
715
+ ".csv": "text/csv",
716
+ # Presentations
717
+ ".ppt": "application/vnd.ms-powerpoint",
718
+ ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
719
+ # Other common formats
720
+ ".json": "application/json",
721
+ ".xml": "application/xml",
722
+ ".zip": "application/zip",
723
+ }
724
+ return file_type_map.get(file_extension, "application/octet-stream")
725
+
726
+ def _create_file_metadata_from_url(
727
+ self, url: str, detail: str = "auto"
728
+ ) -> FileMetadata:
729
+ """Create FileMetadata from URL without downloading the image.
730
+
731
+ The URL is stored in the source_url field, not file_path, to clearly
732
+ distinguish between local files and remote resources.
733
+ """
734
+ try:
735
+ # Parse URL to get file name
736
+ from urllib.parse import urlparse
737
+
738
+ parsed_url = urlparse(url)
739
+ file_name = os.path.basename(parsed_url.path) or "remote_image"
740
+
741
+ # Ensure file name has an extension
742
+ if not os.path.splitext(file_name)[1]:
743
+ file_name += ".jpg" # Default to jpg for images without extension
744
+
745
+ # Determine MIME type from extension or default to image/jpeg
746
+ file_extension = os.path.splitext(file_name)[1].lower()
747
+ file_type_map = {
748
+ ".jpg": "image/jpeg",
749
+ ".jpeg": "image/jpeg",
750
+ ".png": "image/png",
751
+ ".gif": "image/gif",
752
+ ".webp": "image/webp",
753
+ ".bmp": "image/bmp",
754
+ ".svg": "image/svg+xml",
755
+ }
756
+ file_type = file_type_map.get(file_extension, "image/jpeg")
757
+
758
+ # Create FileMetadata with URL in source_url field
759
+ file_metadata = self.file_manager.create_file_metadata(
760
+ PydanticFileMetadata(
761
+ organization_id=self.org_id,
762
+ file_name=file_name,
763
+ file_path=None, # No local path for remote URLs
764
+ source_url=url, # Store URL in the dedicated field
765
+ file_type=file_type,
766
+ file_size=None, # Unknown size for remote URLs
767
+ file_creation_date=None,
768
+ file_last_modified_date=None,
769
+ )
770
+ )
771
+
772
+ return file_metadata
773
+
774
+ except Exception as e:
775
+ raise ValueError(f"Failed to create file metadata from URL {url}: {str(e)}")
776
+
777
+ # agents
778
+ def list_agents(
779
+ self,
780
+ query_text: Optional[str] = None,
781
+ tags: Optional[List[str]] = None,
782
+ limit: int = 100,
783
+ cursor: Optional[str] = None,
784
+ parent_id: Optional[str] = None,
785
+ ) -> List[AgentState]:
786
+ self.interface.clear()
787
+
788
+ return self.server.agent_manager.list_agents(
789
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
790
+ tags=tags,
791
+ query_text=query_text,
792
+ limit=limit,
793
+ cursor=cursor,
794
+ parent_id=parent_id,
795
+ )
796
+
797
+ def agent_exists(
798
+ self, agent_id: Optional[str] = None, agent_name: Optional[str] = None
799
+ ) -> bool:
800
+ """
801
+ Check if an agent exists
802
+
803
+ Args:
804
+ agent_id (str): ID of the agent
805
+ agent_name (str): Name of the agent
806
+
807
+ Returns:
808
+ exists (bool): `True` if the agent exists, `False` otherwise
809
+ """
810
+
811
+ if not (agent_id or agent_name):
812
+ raise ValueError("Either agent_id or agent_name must be provided")
813
+ if agent_id and agent_name:
814
+ raise ValueError("Only one of agent_id or agent_name can be provided")
815
+ existing = self.list_agents()
816
+ if agent_id:
817
+ return str(agent_id) in [str(agent.id) for agent in existing]
818
+ else:
819
+ return agent_name in [str(agent.name) for agent in existing]
820
+
821
+ def create_agent(
822
+ self,
823
+ name: Optional[str] = None,
824
+ # agent config
825
+ agent_type: Optional[AgentType] = AgentType.chat_agent,
826
+ # model configs
827
+ embedding_config: Optional[EmbeddingConfig] = None,
828
+ llm_config: Optional[LLMConfig] = None,
829
+ # memory
830
+ memory: Optional[Memory] = None,
831
+ block_ids: Optional[List[str]] = None,
832
+ system: Optional[str] = None,
833
+ # tools
834
+ tool_ids: Optional[List[str]] = None,
835
+ tool_rules: Optional[List[BaseToolRule]] = None,
836
+ include_base_tools: Optional[bool] = True,
837
+ include_meta_memory_tools: Optional[bool] = False,
838
+ # metadata
839
+ metadata: Optional[Dict] = {
840
+ "human:": DEFAULT_HUMAN,
841
+ "persona": DEFAULT_PERSONA,
842
+ },
843
+ description: Optional[str] = None,
844
+ initial_message_sequence: Optional[List[Message]] = None,
845
+ tags: Optional[List[str]] = None,
846
+ ) -> AgentState:
847
+ """Create an agent
848
+
849
+ Args:
850
+ name (str): Name of the agent
851
+ embedding_config (EmbeddingConfig): Embedding configuration
852
+ llm_config (LLMConfig): LLM configuration
853
+ memory_blocks (List[Dict]): List of configurations for the memory blocks (placed in core-memory)
854
+ system (str): System configuration
855
+ tools (List[str]): List of tools
856
+ tool_rules (Optional[List[BaseToolRule]]): List of tool rules
857
+ include_base_tools (bool): Include base tools
858
+ metadata (Dict): Metadata
859
+ description (str): Description
860
+ tags (List[str]): Tags for filtering agents
861
+
862
+ Returns:
863
+ agent_state (AgentState): State of the created agent
864
+ """
865
+ # construct list of tools
866
+ tool_ids = tool_ids or []
867
+ tool_names = []
868
+ if include_base_tools:
869
+ tool_names += BASE_TOOLS
870
+ if include_meta_memory_tools:
871
+ tool_names += META_MEMORY_TOOLS
872
+ tool_ids += [
873
+ self.server.tool_manager.get_tool_by_name(
874
+ tool_name=name,
875
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
876
+ ).id
877
+ for name in tool_names
878
+ ]
879
+
880
+ # check if default configs are provided
881
+ assert embedding_config or self._default_embedding_config, (
882
+ "Embedding config must be provided"
883
+ )
884
+ assert llm_config or self._default_llm_config, "LLM config must be provided"
885
+
886
+ # TODO: This should not happen here, we need to have clear separation between create/add blocks
887
+ for block in memory.get_blocks():
888
+ self.server.block_manager.create_or_update_block(
889
+ block, actor=self.server.user_manager.get_user_by_id(self.user.id)
890
+ )
891
+
892
+ # Also get any existing block_ids passed in
893
+ block_ids = block_ids or []
894
+
895
+ # create agent
896
+ # Create the base parameters
897
+ create_params = {
898
+ "description": description,
899
+ "metadata_": metadata,
900
+ "memory_blocks": [],
901
+ "block_ids": [b.id for b in memory.get_blocks()] + block_ids,
902
+ "tool_ids": tool_ids,
903
+ "tool_rules": tool_rules,
904
+ "include_base_tools": include_base_tools,
905
+ "system": system,
906
+ "agent_type": agent_type,
907
+ "llm_config": llm_config if llm_config else self._default_llm_config,
908
+ "embedding_config": embedding_config
909
+ if embedding_config
910
+ else self._default_embedding_config,
911
+ "initial_message_sequence": initial_message_sequence,
912
+ "tags": tags,
913
+ }
914
+
915
+ # Only add name if it's not None
916
+ if name is not None:
917
+ create_params["name"] = name
918
+
919
+ agent_state = self.server.create_agent(
920
+ CreateAgent(**create_params),
921
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
922
+ )
923
+
924
+ # TODO: get full agent state
925
+ return self.server.agent_manager.get_agent_by_id(
926
+ agent_state.id, actor=self.server.user_manager.get_user_by_id(self.user.id)
927
+ )
928
+
929
+ def create_user(self, user_id: str, user_name: str) -> PydanticUser:
930
+ return self.server.user_manager.create_user(UserCreate(id=user_id, name=user_name))
931
+
932
+ def create_meta_agent(self, config: dict):
933
+ """Create a MetaAgent for memory management operations.
934
+
935
+ Args:
936
+ config (dict): Configuration dictionary for creating the MetaAgent.
937
+ Can include: name, agents, system_prompts_folder, system_prompts,
938
+ llm_config, embedding_config, memory_blocks, description.
939
+
940
+ Returns:
941
+ MetaAgent: The initialized MetaAgent instance
942
+ """
943
+ # Create MetaAgent through server
944
+ return self.server.create_meta_agent(
945
+ request=CreateMetaAgent(**config),
946
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
947
+ )
948
+
949
+ def get_tools_from_agent(self, agent_id: str) -> List[Tool]:
950
+ """
951
+ Get tools from an existing agent.
952
+
953
+ Args:
954
+ agent_id (str): ID of the agent
955
+
956
+ Returns:
957
+ List[Tool]: A list of Tool objs
958
+ """
959
+ self.interface.clear()
960
+ return self.server.agent_manager.get_agent_by_id(
961
+ agent_id=agent_id,
962
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
963
+ ).tools
964
+
965
+ def add_tool_to_agent(self, agent_id: str, tool_id: str):
966
+ """
967
+ Add tool to an existing agent
968
+
969
+ Args:
970
+ agent_id (str): ID of the agent
971
+ tool_id (str): A tool id
972
+
973
+ Returns:
974
+ agent_state (AgentState): State of the updated agent
975
+ """
976
+ self.interface.clear()
977
+ agent_state = self.server.agent_manager.attach_tool(
978
+ agent_id=agent_id,
979
+ tool_id=tool_id,
980
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
981
+ )
982
+ return agent_state
983
+
984
+ def remove_tool_from_agent(self, agent_id: str, tool_id: str):
985
+ """
986
+ Removes tools from an existing agent
987
+
988
+ Args:
989
+ agent_id (str): ID of the agent
990
+ tool_id (str): The tool id
991
+
992
+ Returns:
993
+ agent_state (AgentState): State of the updated agent
994
+ """
995
+ self.interface.clear()
996
+ agent_state = self.server.agent_manager.detach_tool(
997
+ agent_id=agent_id,
998
+ tool_id=tool_id,
999
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1000
+ )
1001
+ return agent_state
1002
+
1003
+ def rename_agent(self, agent_id: str, new_name: str):
1004
+ """
1005
+ Rename an agent
1006
+
1007
+ Args:
1008
+ agent_id (str): ID of the agent
1009
+ new_name (str): New name for the agent
1010
+ """
1011
+ self.update_agent(agent_id, name=new_name)
1012
+
1013
+ def delete_agent(self, agent_id: str):
1014
+ """
1015
+ Delete an agent
1016
+
1017
+ Args:
1018
+ agent_id (str): ID of the agent to delete
1019
+ """
1020
+ self.server.agent_manager.delete_agent(
1021
+ agent_id=agent_id,
1022
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1023
+ )
1024
+
1025
+ def get_agent_by_name(self, agent_name: str) -> AgentState:
1026
+ """
1027
+ Get an agent by its name
1028
+
1029
+ Args:
1030
+ agent_name (str): Name of the agent
1031
+
1032
+ Returns:
1033
+ agent_state (AgentState): State of the agent
1034
+ """
1035
+ self.interface.clear()
1036
+ return self.server.agent_manager.get_agent_by_name(
1037
+ agent_name=agent_name,
1038
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1039
+ )
1040
+
1041
+ def get_agent(self, agent_id: str) -> AgentState:
1042
+ """
1043
+ Get an agent's state by its ID.
1044
+
1045
+ Args:
1046
+ agent_id (str): ID of the agent
1047
+
1048
+ Returns:
1049
+ agent_state (AgentState): State representation of the agent
1050
+ """
1051
+ self.interface.clear()
1052
+ return self.server.agent_manager.get_agent_by_id(
1053
+ agent_id=agent_id,
1054
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1055
+ )
1056
+
1057
+ def get_agent_id(self, agent_name: str) -> Optional[str]:
1058
+ """
1059
+ Get the ID of an agent by name (names are unique per user)
1060
+
1061
+ Args:
1062
+ agent_name (str): Name of the agent
1063
+
1064
+ Returns:
1065
+ agent_id (str): ID of the agent
1066
+ """
1067
+
1068
+ self.interface.clear()
1069
+ assert agent_name, "Agent name must be provided"
1070
+
1071
+ # TODO: Refactor this futher to not have downstream users expect Optionals - this should just error
1072
+ try:
1073
+ return self.server.agent_manager.get_agent_by_name(
1074
+ agent_name=agent_name,
1075
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1076
+ ).id
1077
+ except NoResultFound:
1078
+ return None
1079
+
1080
+ # memory
1081
+ def get_in_context_memory(self, agent_id: str) -> Memory:
1082
+ """
1083
+ Get the in-context (i.e. core) memory of an agent
1084
+
1085
+ Args:
1086
+ agent_id (str): ID of the agent
1087
+
1088
+ Returns:
1089
+ memory (Memory): In-context memory of the agent
1090
+ """
1091
+ memory = self.server.get_agent_memory(
1092
+ agent_id=agent_id,
1093
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1094
+ )
1095
+ return memory
1096
+
1097
+ def get_core_memory(self, agent_id: str) -> Memory:
1098
+ return self.get_in_context_memory(agent_id)
1099
+
1100
+ def update_in_context_memory(
1101
+ self, agent_id: str, section: str, value: Union[List[str], str]
1102
+ ) -> Memory:
1103
+ """
1104
+ Update the in-context memory of an agent
1105
+
1106
+ Args:
1107
+ agent_id (str): ID of the agent
1108
+
1109
+ Returns:
1110
+ memory (Memory): The updated in-context memory of the agent
1111
+
1112
+ """
1113
+ # TODO: implement this (not sure what it should look like)
1114
+ memory = self.server.update_agent_core_memory(
1115
+ agent_id=agent_id,
1116
+ label=section,
1117
+ value=value,
1118
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1119
+ )
1120
+ return memory
1121
+
1122
+ def get_archival_memory_summary(self, agent_id: str) -> ArchivalMemorySummary:
1123
+ """
1124
+ Get a summary of the archival memory of an agent
1125
+
1126
+ Args:
1127
+ agent_id (str): ID of the agent
1128
+
1129
+ Returns:
1130
+ summary (ArchivalMemorySummary): Summary of the archival memory
1131
+
1132
+ """
1133
+ return self.server.get_archival_memory_summary(
1134
+ agent_id=agent_id,
1135
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1136
+ )
1137
+
1138
+ def get_recall_memory_summary(self, agent_id: str) -> RecallMemorySummary:
1139
+ """
1140
+ Get a summary of the recall memory of an agent
1141
+
1142
+ Args:
1143
+ agent_id (str): ID of the agent
1144
+
1145
+ Returns:
1146
+ summary (RecallMemorySummary): Summary of the recall memory
1147
+ """
1148
+ return self.server.get_recall_memory_summary(
1149
+ agent_id=agent_id,
1150
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1151
+ )
1152
+
1153
+ def get_in_context_messages(self, agent_id: str) -> List[Message]:
1154
+ """
1155
+ Get in-context messages of an agent
1156
+
1157
+ Args:
1158
+ agent_id (str): ID of the agent
1159
+
1160
+ Returns:
1161
+ messages (List[Message]): List of in-context messages
1162
+ """
1163
+ return self.server.agent_manager.get_in_context_messages(
1164
+ agent_id=agent_id,
1165
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1166
+ )
1167
+
1168
+ # agent interactions
1169
+
1170
+ def construct_system_message(
1171
+ self, agent_id: str, message: str, user_id: str
1172
+ ) -> str:
1173
+ """
1174
+ Construct a system message from a message.
1175
+ """
1176
+ return self.server.construct_system_message(
1177
+ agent_id=agent_id,
1178
+ message=message,
1179
+ actor=self.server.user_manager.get_user_by_id(user_id),
1180
+ )
1181
+
1182
+ def extract_memory_for_system_prompt(
1183
+ self, agent_id: str, message: str, user_id: str
1184
+ ) -> str:
1185
+ """
1186
+ Extract memory for system prompt from a message.
1187
+ """
1188
+ return self.server.extract_memory_for_system_prompt(
1189
+ agent_id=agent_id,
1190
+ message=message,
1191
+ actor=self.server.user_manager.get_user_by_id(user_id),
1192
+ )
1193
+
1194
+ def send_messages(
1195
+ self,
1196
+ agent_id: str,
1197
+ messages: List[Union[Message | MessageCreate]],
1198
+ ):
1199
+ """
1200
+ Send pre-packed messages to an agent.
1201
+
1202
+ Args:
1203
+ agent_id (str): ID of the agent
1204
+ messages (List[Union[Message | MessageCreate]]): List of messages to send
1205
+
1206
+ Returns:
1207
+ response (MirixResponse): Response from the agent
1208
+ """
1209
+ self.interface.clear()
1210
+ usage = self.server.send_messages(
1211
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1212
+ agent_id=agent_id,
1213
+ input_messages=messages,
1214
+ )
1215
+
1216
+ # format messages
1217
+ return MirixResponse(messages=messages, usage=usage)
1218
+
1219
+ def send_message(
1220
+ self,
1221
+ message: str | list[dict],
1222
+ role: str,
1223
+ name: Optional[str] = None,
1224
+ agent_id: Optional[str] = None,
1225
+ agent_name: Optional[str] = None,
1226
+ stream_steps: bool = False,
1227
+ stream_tokens: bool = False,
1228
+ chaining: Optional[bool] = None,
1229
+ verbose: Optional[bool] = None,
1230
+ ) -> MirixResponse:
1231
+ """
1232
+ Send a message to an agent
1233
+
1234
+ Args:
1235
+ message (str): Message to send
1236
+ role (str): Role of the message
1237
+ agent_id (str): ID of the agent
1238
+ name(str): Name of the sender
1239
+ stream_steps (bool): Stream the steps (default: `False`)
1240
+ stream_tokens (bool): Stream the tokens (default: `False`)
1241
+ chaining (bool): Whether to enable chaining for this message
1242
+ verbose (bool): Whether to print verbose logging (default: `None`, which inherits from MIRIX_VERBOSE env var)
1243
+
1244
+ Returns:
1245
+ response (MirixResponse): Response from the agent
1246
+ """
1247
+
1248
+ if not agent_id:
1249
+ # lookup agent by name
1250
+ assert agent_name, "Either agent_id or agent_name must be provided"
1251
+ agent_id = self.get_agent_id(agent_name=agent_name)
1252
+ assert agent_id, f"Agent with name {agent_name} not found"
1253
+
1254
+ if stream_steps or stream_tokens:
1255
+ # TODO: implement streaming with stream=True/False
1256
+ raise NotImplementedError
1257
+ self.interface.clear()
1258
+
1259
+ if isinstance(message, str):
1260
+ content = [TextContent(text=message)]
1261
+ input_messages = [
1262
+ MessageCreate(role=MessageRole(role), content=content, name=name)
1263
+ ]
1264
+ elif isinstance(message, list):
1265
+
1266
+ def convert_message(m):
1267
+ if m["type"] == "text":
1268
+ return TextContent(**m)
1269
+ elif m["type"] == "image_url":
1270
+ url = m["image_url"]["url"]
1271
+ detail = m["image_url"].get("detail", "auto")
1272
+
1273
+ # Handle the image based on URL type
1274
+ if url.startswith("data:"):
1275
+ # Base64 encoded image - save locally
1276
+ file_metadata = self._save_image_from_base64(url, detail)
1277
+ else:
1278
+ # HTTP URL - just create FileMetadata without downloading
1279
+ file_metadata = self._create_file_metadata_from_url(url, detail)
1280
+
1281
+ return ImageContent(
1282
+ type=MessageContentType.image_url,
1283
+ image_id=file_metadata.id,
1284
+ detail=detail,
1285
+ )
1286
+
1287
+ elif m["type"] == "image_data":
1288
+ # Base64 image data (new format)
1289
+ data = m["image_data"]["data"]
1290
+ detail = m["image_data"].get("detail", "auto")
1291
+
1292
+ # Save the base64 image to file_manager
1293
+ file_metadata = self._save_image_from_base64(data, detail)
1294
+
1295
+ return ImageContent(
1296
+ type=MessageContentType.image_url,
1297
+ image_id=file_metadata.id,
1298
+ detail=detail,
1299
+ )
1300
+ elif m["type"] == "file_uri":
1301
+ # File URI (local file path)
1302
+ file_path = m["file_uri"]
1303
+
1304
+ # Check if it's an image or other file type
1305
+ file_type = self._determine_file_type(file_path)
1306
+
1307
+ if file_type.startswith("image/"):
1308
+ # Handle as image
1309
+ file_metadata = self._save_image_from_file_uri(file_path)
1310
+ return ImageContent(
1311
+ type=MessageContentType.image_url,
1312
+ image_id=file_metadata.id,
1313
+ detail="auto",
1314
+ )
1315
+ else:
1316
+ # Handle as general file (e.g., PDF, DOC, etc.)
1317
+ file_metadata = self._save_file_from_path(file_path)
1318
+ return FileContent(
1319
+ type=MessageContentType.file_uri, file_id=file_metadata.id
1320
+ )
1321
+
1322
+ elif m["type"] == "google_cloud_file_uri":
1323
+ # Google Cloud file URI
1324
+ # Handle both the typo version and the correct version from the test file
1325
+ file_uri = m.get("google_cloud_file_uri") or m.get("file_uri")
1326
+
1327
+ file_metadata = self._save_image_from_google_cloud_uri(file_uri)
1328
+ return CloudFileContent(
1329
+ type=MessageContentType.google_cloud_file_uri,
1330
+ cloud_file_uri=file_metadata.id,
1331
+ )
1332
+
1333
+ elif m["type"] == "database_image_id":
1334
+ return ImageContent(
1335
+ type=MessageContentType.image_url,
1336
+ image_id=m["image_id"],
1337
+ detail="auto",
1338
+ )
1339
+
1340
+ elif m["type"] == "database_file_id":
1341
+ return FileContent(
1342
+ type=MessageContentType.file_uri,
1343
+ file_id=m["file_id"],
1344
+ )
1345
+
1346
+ elif m["type"] == "database_google_cloud_file_uri":
1347
+ return CloudFileContent(
1348
+ type=MessageContentType.google_cloud_file_uri,
1349
+ cloud_file_uri=m["cloud_file_uri"],
1350
+ )
1351
+
1352
+ else:
1353
+ raise ValueError(f"Unknown message type: {m['type']}")
1354
+
1355
+ content = [convert_message(m) for m in message]
1356
+ input_messages = [
1357
+ MessageCreate(role=MessageRole(role), content=content, name=name)
1358
+ ]
1359
+ if extra_messages is not None:
1360
+ extra_messages = [
1361
+ MessageCreate(
1362
+ role=MessageRole(role),
1363
+ content=[convert_message(m) for m in extra_messages],
1364
+ name=name,
1365
+ )
1366
+ ]
1367
+
1368
+ else:
1369
+ raise ValueError(f"Invalid message type: {type(message)}")
1370
+
1371
+ usage = self.server.send_messages(
1372
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1373
+ agent_id=agent_id,
1374
+ input_messages=input_messages,
1375
+ chaining=chaining,
1376
+ verbose=verbose,
1377
+ )
1378
+
1379
+ # format messages
1380
+ messages = self.interface.to_list()
1381
+
1382
+ mirix_messages = []
1383
+ for m in messages:
1384
+ mirix_messages += m.to_mirix_message()
1385
+
1386
+ return MirixResponse(messages=mirix_messages, usage=usage)
1387
+
1388
+ def user_message(self, agent_id: str, message: str) -> MirixResponse:
1389
+ """
1390
+ Send a message to an agent as a user
1391
+
1392
+ Args:
1393
+ agent_id (str): ID of the agent
1394
+ message (str): Message to send
1395
+
1396
+ Returns:
1397
+ response (MirixResponse): Response from the agent
1398
+ """
1399
+ self.interface.clear()
1400
+ return self.send_message(role="user", agent_id=agent_id, message=message)
1401
+
1402
+ def run_command(self, agent_id: str, command: str) -> MirixResponse:
1403
+ """
1404
+ Run a command on the agent
1405
+
1406
+ Args:
1407
+ agent_id (str): The agent ID
1408
+ command (str): The command to run
1409
+
1410
+ Returns:
1411
+ MirixResponse: The response from the agent
1412
+
1413
+ """
1414
+ self.interface.clear()
1415
+ usage = self.server.run_command(
1416
+ user_id=self.user_id, agent_id=agent_id, command=command
1417
+ )
1418
+
1419
+ # NOTE: messages/usage may be empty, depending on the command
1420
+ return MirixResponse(messages=self.interface.to_list(), usage=usage)
1421
+
1422
+ # archival memory
1423
+
1424
+ # humans / personas
1425
+
1426
+ def get_block_id(self, name: str, label: str) -> str:
1427
+ block = self.server.block_manager.get_blocks(
1428
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1429
+ label=label,
1430
+ )
1431
+ if not block:
1432
+ return None
1433
+ return block[0].id
1434
+
1435
+ def create_human(self, name: str, text: str):
1436
+ """
1437
+ Create a human block template (saved human string to pre-fill `ChatMemory`)
1438
+
1439
+ Args:
1440
+ name (str): Name of the human block
1441
+ text (str): Text of the human block
1442
+
1443
+ Returns:
1444
+ human (Human): Human block
1445
+ """
1446
+ return self.server.block_manager.create_or_update_block(
1447
+ Human(value=text),
1448
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1449
+ )
1450
+
1451
+ def create_persona(self, name: str, text: str):
1452
+ """
1453
+ Create a persona block template (saved persona string to pre-fill `ChatMemory`)
1454
+
1455
+ Args:
1456
+ name (str): Name of the persona block
1457
+ text (str): Text of the persona block
1458
+
1459
+ Returns:
1460
+ persona (Persona): Persona block
1461
+ """
1462
+ return self.server.block_manager.create_or_update_block(
1463
+ Persona(value=text),
1464
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1465
+ )
1466
+
1467
+ def list_humans(self):
1468
+ """
1469
+ List available human block templates
1470
+
1471
+ Returns:
1472
+ humans (List[Human]): List of human blocks
1473
+ """
1474
+ return self.server.block_manager.get_blocks(
1475
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1476
+ label="human",
1477
+ )
1478
+
1479
+ def list_personas(self) -> List[Persona]:
1480
+ """
1481
+ List available persona block templates
1482
+
1483
+ Returns:
1484
+ personas (List[Persona]): List of persona blocks
1485
+ """
1486
+ return self.server.block_manager.get_blocks(
1487
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1488
+ label="persona",
1489
+ )
1490
+
1491
+ def update_human(self, human_id: str, text: str):
1492
+ """
1493
+ Update a human block template
1494
+
1495
+ Args:
1496
+ human_id (str): ID of the human block
1497
+ text (str): Text of the human block
1498
+
1499
+ Returns:
1500
+ human (Human): Updated human block
1501
+ """
1502
+
1503
+ return self.server.block_manager.update_block(
1504
+ block_id=human_id,
1505
+ block_update=BlockUpdate(value=text),
1506
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1507
+ )
1508
+
1509
+ def update_persona(self, persona_id: str, text: str):
1510
+ """
1511
+ Update a persona block template
1512
+
1513
+ Args:
1514
+ persona_id (str): ID of the persona block
1515
+ text (str): Text of the persona block
1516
+
1517
+ Returns:
1518
+ persona (Persona): Updated persona block
1519
+ """
1520
+ blocks = self.server.block_manager.get_blocks(self.user)
1521
+ persona_block = [block for block in blocks if block.label == "persona"][0]
1522
+ return self.server.block_manager.update_block(
1523
+ block_id=persona_block.id,
1524
+ block_update=BlockUpdate(value=text),
1525
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1526
+ )
1527
+
1528
+ def update_persona_text(self, persona_name: str, text: str):
1529
+ """
1530
+ Update a persona block template by template name
1531
+
1532
+ Args:
1533
+ persona_name (str): Name of the persona template
1534
+ text (str): Text of the persona block
1535
+
1536
+ Returns:
1537
+ persona (Persona): Updated persona block
1538
+ """
1539
+ persona_id = self.get_persona_id(persona_name)
1540
+ if persona_id:
1541
+ # Update existing persona
1542
+ return self.server.block_manager.update_block(
1543
+ block_id=persona_id,
1544
+ block_update=BlockUpdate(value=text),
1545
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1546
+ )
1547
+ else:
1548
+ # Create new persona if it doesn't exist
1549
+ return self.create_persona(persona_name, text)
1550
+
1551
+ def get_persona(self, id: str) -> Persona:
1552
+ """
1553
+ Get a persona block template
1554
+
1555
+ Args:
1556
+ id (str): ID of the persona block
1557
+
1558
+ Returns:
1559
+ persona (Persona): Persona block
1560
+ """
1561
+ assert id, "Persona ID must be provided"
1562
+ return Persona(
1563
+ **self.server.block_manager.get_block_by_id(
1564
+ id, actor=self.server.user_manager.get_user_by_id(self.user.id)
1565
+ ).model_dump()
1566
+ )
1567
+
1568
+ def get_human(self, id: str) -> Human:
1569
+ """
1570
+ Get a human block template
1571
+
1572
+ Args:
1573
+ id (str): ID of the human block
1574
+
1575
+ Returns:
1576
+ human (Human): Human block
1577
+ """
1578
+ assert id, "Human ID must be provided"
1579
+ return Human(
1580
+ **self.server.block_manager.get_block_by_id(
1581
+ id, actor=self.server.user_manager.get_user_by_id(self.user.id)
1582
+ ).model_dump()
1583
+ )
1584
+
1585
+ def get_persona_id(self, name: str) -> str:
1586
+ """
1587
+ Get the ID of a persona block template
1588
+
1589
+ Args:
1590
+ name (str): Name of the persona block
1591
+
1592
+ Returns:
1593
+ id (str): ID of the persona block
1594
+ """
1595
+ persona = self.server.block_manager.get_blocks(
1596
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1597
+ label="persona",
1598
+ )
1599
+ if not persona:
1600
+ return None
1601
+ return persona[0].id
1602
+
1603
+ def get_human_id(self, name: str) -> str:
1604
+ """
1605
+ Get the ID of a human block template
1606
+
1607
+ Args:
1608
+ name (str): Name of the human block
1609
+
1610
+ Returns:
1611
+ id (str): ID of the human block
1612
+ """
1613
+ human = self.server.block_manager.get_blocks(
1614
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1615
+ label="human",
1616
+ )
1617
+ if not human:
1618
+ return None
1619
+ return human[0].id
1620
+
1621
+ def delete_persona(self, id: str):
1622
+ """
1623
+ Delete a persona block template
1624
+
1625
+ Args:
1626
+ id (str): ID of the persona block
1627
+ """
1628
+ self.delete_block(id)
1629
+
1630
+ def delete_human(self, id: str):
1631
+ """
1632
+ Delete a human block template
1633
+
1634
+ Args:
1635
+ id (str): ID of the human block
1636
+ """
1637
+ self.delete_block(id)
1638
+
1639
+ # tools
1640
+ def load_langchain_tool(
1641
+ self,
1642
+ langchain_tool: "LangChainBaseTool",
1643
+ additional_imports_module_attr_map: dict[str, str] = None,
1644
+ ) -> Tool:
1645
+ tool_create = ToolCreate.from_langchain(
1646
+ langchain_tool=langchain_tool,
1647
+ additional_imports_module_attr_map=additional_imports_module_attr_map,
1648
+ )
1649
+ return self.server.tool_manager.create_or_update_tool(
1650
+ pydantic_tool=Tool(**tool_create.model_dump()),
1651
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1652
+ )
1653
+
1654
+ def load_crewai_tool(
1655
+ self,
1656
+ crewai_tool: "CrewAIBaseTool",
1657
+ additional_imports_module_attr_map: dict[str, str] = None,
1658
+ ) -> Tool:
1659
+ tool_create = ToolCreate.from_crewai(
1660
+ crewai_tool=crewai_tool,
1661
+ additional_imports_module_attr_map=additional_imports_module_attr_map,
1662
+ )
1663
+ return self.server.tool_manager.create_or_update_tool(
1664
+ pydantic_tool=Tool(**tool_create.model_dump()),
1665
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1666
+ )
1667
+
1668
+ def load_composio_tool(self, action: "ActionType") -> Tool:
1669
+ tool_create = ToolCreate.from_composio(action_name=action.name)
1670
+ return self.server.tool_manager.create_or_update_tool(
1671
+ pydantic_tool=Tool(**tool_create.model_dump()),
1672
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1673
+ )
1674
+
1675
+ def create_tool(
1676
+ self,
1677
+ func,
1678
+ name: Optional[str] = None,
1679
+ tags: Optional[List[str]] = None,
1680
+ description: Optional[str] = None,
1681
+ return_char_limit: int = FUNCTION_RETURN_CHAR_LIMIT,
1682
+ ) -> Tool:
1683
+ """
1684
+ Create a tool. This stores the source code of function on the server, so that the server can execute the function and generate an OpenAI JSON schemas for it when using with an agent.
1685
+
1686
+ Args:
1687
+ func (callable): The function to create a tool for.
1688
+ name: (str): Name of the tool (must be unique per-user.)
1689
+ tags (Optional[List[str]], optional): Tags for the tool. Defaults to None.
1690
+ description (str, optional): The description.
1691
+ return_char_limit (int): The character limit for the tool's return value. Defaults to FUNCTION_RETURN_CHAR_LIMIT.
1692
+
1693
+ Returns:
1694
+ tool (Tool): The created tool.
1695
+ """
1696
+ # TODO: check if tool already exists
1697
+ # TODO: how to load modules?
1698
+ # parse source code/schema
1699
+ source_code = parse_source_code(func)
1700
+ source_type = "python"
1701
+ if not tags:
1702
+ tags = []
1703
+
1704
+ # call server function
1705
+ return self.server.tool_manager.create_tool(
1706
+ Tool(
1707
+ source_type=source_type,
1708
+ source_code=source_code,
1709
+ name=name,
1710
+ tags=tags,
1711
+ description=description,
1712
+ return_char_limit=return_char_limit,
1713
+ ),
1714
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1715
+ )
1716
+
1717
+ def create_or_update_tool(
1718
+ self,
1719
+ func,
1720
+ name: Optional[str] = None,
1721
+ tags: Optional[List[str]] = None,
1722
+ description: Optional[str] = None,
1723
+ return_char_limit: int = FUNCTION_RETURN_CHAR_LIMIT,
1724
+ ) -> Tool:
1725
+ """
1726
+ Creates or updates a tool. This stores the source code of function on the server, so that the server can execute the function and generate an OpenAI JSON schemas for it when using with an agent.
1727
+
1728
+ Args:
1729
+ func (callable): The function to create a tool for.
1730
+ name: (str): Name of the tool (must be unique per-user.)
1731
+ tags (Optional[List[str]], optional): Tags for the tool. Defaults to None.
1732
+ description (str, optional): The description.
1733
+ return_char_limit (int): The character limit for the tool's return value. Defaults to FUNCTION_RETURN_CHAR_LIMIT.
1734
+
1735
+ Returns:
1736
+ tool (Tool): The created tool.
1737
+ """
1738
+ source_code = parse_source_code(func)
1739
+ source_type = "python"
1740
+ if not tags:
1741
+ tags = []
1742
+
1743
+ # call server function
1744
+ return self.server.tool_manager.create_or_update_tool(
1745
+ Tool(
1746
+ source_type=source_type,
1747
+ source_code=source_code,
1748
+ name=name,
1749
+ tags=tags,
1750
+ description=description,
1751
+ return_char_limit=return_char_limit,
1752
+ ),
1753
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1754
+ )
1755
+
1756
+ def update_tool(
1757
+ self,
1758
+ id: str,
1759
+ name: Optional[str] = None,
1760
+ description: Optional[str] = None,
1761
+ func: Optional[callable] = None,
1762
+ tags: Optional[List[str]] = None,
1763
+ return_char_limit: int = FUNCTION_RETURN_CHAR_LIMIT,
1764
+ ) -> Tool:
1765
+ """
1766
+ Update a tool with provided parameters (name, func, tags)
1767
+
1768
+ Args:
1769
+ id (str): ID of the tool
1770
+ name (str): Name of the tool
1771
+ func (callable): Function to wrap in a tool
1772
+ tags (List[str]): Tags for the tool
1773
+ return_char_limit (int): The character limit for the tool's return value. Defaults to FUNCTION_RETURN_CHAR_LIMIT.
1774
+
1775
+ Returns:
1776
+ tool (Tool): Updated tool
1777
+ """
1778
+ update_data = {
1779
+ "source_type": "python", # Always include source_type
1780
+ "source_code": parse_source_code(func) if func else None,
1781
+ "tags": tags,
1782
+ "name": name,
1783
+ "description": description,
1784
+ "return_char_limit": return_char_limit,
1785
+ }
1786
+
1787
+ # Filter out any None values from the dictionary
1788
+ update_data = {
1789
+ key: value for key, value in update_data.items() if value is not None
1790
+ }
1791
+
1792
+ return self.server.tool_manager.update_tool_by_id(
1793
+ tool_id=id,
1794
+ tool_update=ToolUpdate(**update_data),
1795
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1796
+ )
1797
+
1798
+ def list_tools(
1799
+ self, cursor: Optional[str] = None, limit: Optional[int] = 50
1800
+ ) -> List[Tool]:
1801
+ """
1802
+ List available tools for the user.
1803
+
1804
+ Returns:
1805
+ tools (List[Tool]): List of tools
1806
+ """
1807
+ return self.server.tool_manager.list_tools(
1808
+ cursor=cursor,
1809
+ limit=limit,
1810
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1811
+ )
1812
+
1813
+ def get_tool(self, id: str) -> Optional[Tool]:
1814
+ """
1815
+ Get a tool given its ID.
1816
+
1817
+ Args:
1818
+ id (str): ID of the tool
1819
+
1820
+ Returns:
1821
+ tool (Tool): Tool
1822
+ """
1823
+ return self.server.tool_manager.get_tool_by_id(
1824
+ id, actor=self.server.user_manager.get_user_by_id(self.user.id)
1825
+ )
1826
+
1827
+ def delete_tool(self, id: str):
1828
+ """
1829
+ Delete a tool given the ID.
1830
+
1831
+ Args:
1832
+ id (str): ID of the tool
1833
+ """
1834
+ return self.server.tool_manager.delete_tool_by_id(
1835
+ id, actor=self.server.user_manager.get_user_by_id(self.user.id)
1836
+ )
1837
+
1838
+ def get_tool_id(self, name: str) -> Optional[str]:
1839
+ """
1840
+ Get the ID of a tool from its name. The client will use the org_id it is configured with.
1841
+
1842
+ Args:
1843
+ name (str): Name of the tool
1844
+
1845
+ Returns:
1846
+ id (str): ID of the tool (`None` if not found)
1847
+ """
1848
+ tool = self.server.tool_manager.get_tool_by_name(
1849
+ tool_name=name, actor=self.server.user_manager.get_user_by_id(self.user.id)
1850
+ )
1851
+ return tool.id if tool else None
1852
+
1853
+ # recall memory
1854
+
1855
+ def get_messages(
1856
+ self, agent_id: str, cursor: Optional[str] = None, limit: Optional[int] = 1000
1857
+ ) -> List[Message]:
1858
+ """
1859
+ Get messages from an agent with pagination.
1860
+
1861
+ Args:
1862
+ agent_id (str): ID of the agent
1863
+ cursor (str): Get messages after a certain time
1864
+ limit (int): Limit number of messages
1865
+
1866
+ Returns:
1867
+ messages (List[Message]): List of messages
1868
+ """
1869
+
1870
+ self.interface.clear()
1871
+ return self.server.get_agent_recall_cursor(
1872
+ user_id=self.user_id,
1873
+ agent_id=agent_id,
1874
+ before=cursor,
1875
+ limit=limit,
1876
+ reverse=True,
1877
+ )
1878
+
1879
+ def list_blocks(
1880
+ self, label: Optional[str] = None, templates_only: Optional[bool] = True
1881
+ ) -> List[Block]:
1882
+ """
1883
+ List available blocks
1884
+
1885
+ Args:
1886
+ label (str): Label of the block
1887
+ templates_only (bool): List only templates
1888
+
1889
+ Returns:
1890
+ blocks (List[Block]): List of blocks
1891
+ """
1892
+ blocks = self.server.block_manager.get_blocks(
1893
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
1894
+ label=label,
1895
+ )
1896
+ return blocks
1897
+
1898
+ def create_block(
1899
+ self,
1900
+ label: str,
1901
+ value: str,
1902
+ limit: Optional[int] = None,
1903
+ ) -> Block: #
1904
+ """
1905
+ Create a block
1906
+
1907
+ Args:
1908
+ label (str): Label of the block
1909
+ text (str): Text of the block
1910
+ limit (int): Character of the block
1911
+
1912
+ Returns:
1913
+ block (Block): Created block
1914
+ """
1915
+ block = Block(
1916
+ label=label,
1917
+ value=value,
1918
+ limit=limit,
1919
+ )
1920
+ # if limit:
1921
+ # block.limit = limit
1922
+ return self.server.block_manager.create_or_update_block(
1923
+ block, actor=self.server.user_manager.get_user_by_id(self.user.id)
1924
+ )
1925
+
1926
+ def get_block(self, block_id: str) -> Block:
1927
+ """
1928
+ Get a block
1929
+
1930
+ Args:
1931
+ block_id (str): ID of the block
1932
+
1933
+ Returns:
1934
+ block (Block): Block
1935
+ """
1936
+ return self.server.block_manager.get_block_by_id(
1937
+ block_id, actor=self.server.user_manager.get_user_by_id(self.user.id)
1938
+ )
1939
+
1940
+ def delete_block(self, id: str) -> Block:
1941
+ """
1942
+ Delete a block
1943
+
1944
+ Args:
1945
+ id (str): ID of the block
1946
+
1947
+ Returns:
1948
+ block (Block): Deleted block
1949
+ """
1950
+ return self.server.block_manager.delete_block(
1951
+ id, actor=self.server.user_manager.get_user_by_id(self.user.id)
1952
+ )
1953
+
1954
+ def set_default_llm_config(self, llm_config: LLMConfig):
1955
+ """
1956
+ Set the default LLM configuration for agents.
1957
+
1958
+ Args:
1959
+ llm_config (LLMConfig): LLM configuration
1960
+ """
1961
+ self._default_llm_config = llm_config
1962
+
1963
+ def set_default_embedding_config(self, embedding_config: EmbeddingConfig):
1964
+ """
1965
+ Set the default embedding configuration for agents.
1966
+
1967
+ Args:
1968
+ embedding_config (EmbeddingConfig): Embedding configuration
1969
+ """
1970
+ self._default_embedding_config = embedding_config
1971
+
1972
+ def list_llm_configs(self) -> List[LLMConfig]:
1973
+ """
1974
+ List available LLM configurations
1975
+
1976
+ Returns:
1977
+ configs (List[LLMConfig]): List of LLM configurations
1978
+ """
1979
+ return self.server.list_llm_models()
1980
+
1981
+ def list_embedding_configs(self) -> List[EmbeddingConfig]:
1982
+ """
1983
+ List available embedding configurations
1984
+
1985
+ Returns:
1986
+ configs (List[EmbeddingConfig]): List of embedding configurations
1987
+ """
1988
+ return self.server.list_embedding_models()
1989
+
1990
+ def create_org(self, name: Optional[str] = None) -> Organization:
1991
+ return self.server.organization_manager.create_organization(
1992
+ pydantic_org=Organization(name=name)
1993
+ )
1994
+
1995
+ def list_orgs(
1996
+ self, cursor: Optional[str] = None, limit: Optional[int] = 50
1997
+ ) -> List[Organization]:
1998
+ return self.server.organization_manager.list_organizations(
1999
+ cursor=cursor, limit=limit
2000
+ )
2001
+
2002
+ def delete_org(self, org_id: str) -> Organization:
2003
+ return self.server.organization_manager.delete_organization_by_id(org_id=org_id)
2004
+
2005
+ def create_sandbox_config(
2006
+ self, config: Union[LocalSandboxConfig, E2BSandboxConfig]
2007
+ ) -> SandboxConfig:
2008
+ """
2009
+ Create a new sandbox configuration.
2010
+ """
2011
+ config_create = SandboxConfigCreate(config=config)
2012
+ return self.server.sandbox_config_manager.create_or_update_sandbox_config(
2013
+ sandbox_config_create=config_create,
2014
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
2015
+ )
2016
+
2017
+ def update_sandbox_config(
2018
+ self,
2019
+ sandbox_config_id: str,
2020
+ config: Union[LocalSandboxConfig, E2BSandboxConfig],
2021
+ ) -> SandboxConfig:
2022
+ """
2023
+ Update an existing sandbox configuration.
2024
+ """
2025
+ sandbox_update = SandboxConfigUpdate(config=config)
2026
+ return self.server.sandbox_config_manager.update_sandbox_config(
2027
+ sandbox_config_id=sandbox_config_id,
2028
+ sandbox_update=sandbox_update,
2029
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
2030
+ )
2031
+
2032
+ def delete_sandbox_config(self, sandbox_config_id: str) -> None:
2033
+ """
2034
+ Delete a sandbox configuration.
2035
+ """
2036
+ return self.server.sandbox_config_manager.delete_sandbox_config(
2037
+ sandbox_config_id=sandbox_config_id,
2038
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
2039
+ )
2040
+
2041
+ def list_sandbox_configs(
2042
+ self, limit: int = 50, cursor: Optional[str] = None
2043
+ ) -> List[SandboxConfig]:
2044
+ """
2045
+ List all sandbox configurations.
2046
+ """
2047
+ return self.server.sandbox_config_manager.list_sandbox_configs(
2048
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
2049
+ limit=limit,
2050
+ cursor=cursor,
2051
+ )
2052
+
2053
+ def create_sandbox_env_var(
2054
+ self,
2055
+ sandbox_config_id: str,
2056
+ key: str,
2057
+ value: str,
2058
+ description: Optional[str] = None,
2059
+ ) -> SandboxEnvironmentVariable:
2060
+ """
2061
+ Create a new environment variable for a sandbox configuration.
2062
+ """
2063
+ env_var_create = SandboxEnvironmentVariableCreate(
2064
+ key=key, value=value, description=description
2065
+ )
2066
+ return self.server.sandbox_config_manager.create_sandbox_env_var(
2067
+ env_var_create=env_var_create,
2068
+ sandbox_config_id=sandbox_config_id,
2069
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
2070
+ )
2071
+
2072
+ def update_sandbox_env_var(
2073
+ self,
2074
+ env_var_id: str,
2075
+ key: Optional[str] = None,
2076
+ value: Optional[str] = None,
2077
+ description: Optional[str] = None,
2078
+ ) -> SandboxEnvironmentVariable:
2079
+ """
2080
+ Update an existing environment variable.
2081
+ """
2082
+ env_var_update = SandboxEnvironmentVariableUpdate(
2083
+ key=key, value=value, description=description
2084
+ )
2085
+ return self.server.sandbox_config_manager.update_sandbox_env_var(
2086
+ env_var_id=env_var_id,
2087
+ env_var_update=env_var_update,
2088
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
2089
+ )
2090
+
2091
+ def delete_sandbox_env_var(self, env_var_id: str) -> None:
2092
+ """
2093
+ Delete an environment variable by its ID.
2094
+ """
2095
+ return self.server.sandbox_config_manager.delete_sandbox_env_var(
2096
+ env_var_id=env_var_id,
2097
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
2098
+ )
2099
+
2100
+ def list_sandbox_env_vars(
2101
+ self, sandbox_config_id: str, limit: int = 50, cursor: Optional[str] = None
2102
+ ) -> List[SandboxEnvironmentVariable]:
2103
+ """
2104
+ List all environment variables associated with a sandbox configuration.
2105
+ """
2106
+ return self.server.sandbox_config_manager.list_sandbox_env_vars(
2107
+ sandbox_config_id=sandbox_config_id,
2108
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
2109
+ limit=limit,
2110
+ cursor=cursor,
2111
+ )
2112
+
2113
+ # file management methods
2114
+ def save_file(
2115
+ self, file_path: str, source_id: Optional[str] = None
2116
+ ) -> FileMetadata:
2117
+ """
2118
+ Save a file to the file manager and return its metadata.
2119
+
2120
+ Args:
2121
+ file_path (str): Path to the file to save
2122
+ source_id (Optional[str]): Optional source ID to associate with the file
2123
+
2124
+ Returns:
2125
+ FileMetadata: The created file metadata
2126
+ """
2127
+ return self.file_manager.create_file_metadata_from_path(
2128
+ file_path=file_path, organization_id=self.org_id, source_id=source_id
2129
+ )
2130
+
2131
+ def list_files(
2132
+ self, cursor: Optional[str] = None, limit: Optional[int] = 50
2133
+ ) -> List[FileMetadata]:
2134
+ """
2135
+ List files for the current organization.
2136
+
2137
+ Args:
2138
+ cursor (Optional[str]): Pagination cursor
2139
+ limit (Optional[int]): Maximum number of files to return
2140
+
2141
+ Returns:
2142
+ List[FileMetadata]: List of file metadata
2143
+ """
2144
+ return self.file_manager.get_files_by_organization_id(
2145
+ organization_id=self.org_id, cursor=cursor, limit=limit
2146
+ )
2147
+
2148
+ def get_file(self, file_id: str) -> FileMetadata:
2149
+ """
2150
+ Get file metadata by ID.
2151
+
2152
+ Args:
2153
+ file_id (str): ID of the file
2154
+
2155
+ Returns:
2156
+ FileMetadata: The file metadata
2157
+ """
2158
+ return self.file_manager.get_file_metadata_by_id(file_id)
2159
+
2160
+ def delete_file(self, file_id: str) -> None:
2161
+ """
2162
+ Delete a file by ID.
2163
+
2164
+ Args:
2165
+ file_id (str): ID of the file to delete
2166
+ """
2167
+ self.file_manager.delete_file_metadata(file_id)
2168
+
2169
+ def search_files(self, name_pattern: str) -> List[FileMetadata]:
2170
+ """
2171
+ Search files by name pattern.
2172
+
2173
+ Args:
2174
+ name_pattern (str): Pattern to search for in file names
2175
+
2176
+ Returns:
2177
+ List[FileMetadata]: List of matching files
2178
+ """
2179
+ return self.file_manager.search_files_by_name(
2180
+ file_name=name_pattern, organization_id=self.org_id
2181
+ )
2182
+
2183
+ def get_file_stats(self) -> dict:
2184
+ """
2185
+ Get file statistics for the current organization.
2186
+
2187
+ Returns:
2188
+ dict: File statistics including total files, size, and types
2189
+ """
2190
+ return self.file_manager.get_file_stats(organization_id=self.org_id)
2191
+
2192
+ def update_agent_memory_block_label(
2193
+ self, agent_id: str, current_label: str, new_label: str
2194
+ ) -> Memory:
2195
+ """Rename a block in the agent's core memory
2196
+
2197
+ Args:
2198
+ agent_id (str): The agent ID
2199
+ current_label (str): The current label of the block
2200
+ new_label (str): The new label of the block
2201
+
2202
+ Returns:
2203
+ memory (Memory): The updated memory
2204
+ """
2205
+ block = self.get_agent_memory_block(agent_id, current_label)
2206
+ return self.update_block(block.id, label=new_label)
2207
+
2208
+ # TODO: remove this
2209
+ def add_agent_memory_block(
2210
+ self, agent_id: str, create_block: CreateBlock
2211
+ ) -> Memory:
2212
+ """
2213
+ Create and link a memory block to an agent's core memory
2214
+
2215
+ Args:
2216
+ agent_id (str): The agent ID
2217
+ create_block (CreateBlock): The block to create
2218
+
2219
+ Returns:
2220
+ memory (Memory): The updated memory
2221
+ """
2222
+ block_req = Block(**create_block.model_dump())
2223
+ block = self.server.block_manager.create_or_update_block(
2224
+ actor=self.server.user_manager.get_user_by_id(self.user.id), block=block_req
2225
+ )
2226
+ # Link the block to the agent
2227
+ agent = self.server.agent_manager.attach_block(
2228
+ agent_id=agent_id,
2229
+ block_id=block.id,
2230
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
2231
+ )
2232
+ return agent.memory
2233
+
2234
+ def link_agent_memory_block(self, agent_id: str, block_id: str) -> Memory:
2235
+ """
2236
+ Link a block to an agent's core memory
2237
+
2238
+ Args:
2239
+ agent_id (str): The agent ID
2240
+ block_id (str): The block ID
2241
+
2242
+ Returns:
2243
+ memory (Memory): The updated memory
2244
+ """
2245
+ return self.server.agent_manager.attach_block(
2246
+ agent_id=agent_id,
2247
+ block_id=block_id,
2248
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
2249
+ )
2250
+
2251
+ def remove_agent_memory_block(self, agent_id: str, block_label: str) -> Memory:
2252
+ """
2253
+ Unlike a block from the agent's core memory
2254
+
2255
+ Args:
2256
+ agent_id (str): The agent ID
2257
+ block_label (str): The block label
2258
+
2259
+ Returns:
2260
+ memory (Memory): The updated memory
2261
+ """
2262
+ return self.server.agent_manager.detach_block_with_label(
2263
+ agent_id=agent_id,
2264
+ block_label=block_label,
2265
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
2266
+ )
2267
+
2268
+ def list_agent_memory_blocks(self, agent_id: str) -> List[Block]:
2269
+ """
2270
+ Get all the blocks in the agent's core memory
2271
+
2272
+ Args:
2273
+ agent_id (str): The agent ID
2274
+
2275
+ Returns:
2276
+ blocks (List[Block]): The blocks in the agent's core memory
2277
+ """
2278
+ agent = self.server.agent_manager.get_agent_by_id(
2279
+ agent_id=agent_id,
2280
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
2281
+ )
2282
+ return agent.memory.blocks
2283
+
2284
+ def get_agent_memory_block(self, agent_id: str, label: str) -> Block:
2285
+ """
2286
+ Get a block in the agent's core memory by its label
2287
+
2288
+ Args:
2289
+ agent_id (str): The agent ID
2290
+ label (str): The label in the agent's core memory
2291
+
2292
+ Returns:
2293
+ block (Block): The block corresponding to the label
2294
+ """
2295
+ return self.server.agent_manager.get_block_with_label(
2296
+ agent_id=agent_id,
2297
+ block_label=label,
2298
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
2299
+ )
2300
+
2301
+ def update_agent_memory_block(
2302
+ self,
2303
+ agent_id: str,
2304
+ label: str,
2305
+ value: Optional[str] = None,
2306
+ limit: Optional[int] = None,
2307
+ ):
2308
+ """
2309
+ Update a block in the agent's core memory by specifying its label
2310
+
2311
+ Args:
2312
+ agent_id (str): The agent ID
2313
+ label (str): The label of the block
2314
+ value (str): The new value of the block
2315
+ limit (int): The new limit of the block
2316
+
2317
+ Returns:
2318
+ block (Block): The updated block
2319
+ """
2320
+ block = self.get_agent_memory_block(agent_id, label)
2321
+ data = {}
2322
+ if value:
2323
+ data["value"] = value
2324
+ if limit:
2325
+ data["limit"] = limit
2326
+ return self.server.block_manager.update_block(
2327
+ block.id,
2328
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
2329
+ block_update=BlockUpdate(**data),
2330
+ )
2331
+
2332
+ def update_block(
2333
+ self,
2334
+ block_id: str,
2335
+ label: Optional[str] = None,
2336
+ value: Optional[str] = None,
2337
+ limit: Optional[int] = None,
2338
+ ):
2339
+ """
2340
+ Update a block given the ID with the provided fields
2341
+
2342
+ Args:
2343
+ block_id (str): ID of the block
2344
+ label (str): Label to assign to the block
2345
+ value (str): Value to assign to the block
2346
+ limit (int): Token limit to assign to the block
2347
+
2348
+ Returns:
2349
+ block (Block): Updated block
2350
+ """
2351
+ data = {}
2352
+ if value:
2353
+ data["value"] = value
2354
+ if limit:
2355
+ data["limit"] = limit
2356
+ if label:
2357
+ data["label"] = label
2358
+ return self.server.block_manager.update_block(
2359
+ block_id,
2360
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
2361
+ block_update=BlockUpdate(**data),
2362
+ )
2363
+
2364
+ def get_tags(
2365
+ self,
2366
+ cursor: str = None,
2367
+ limit: int = 100,
2368
+ query_text: str = None,
2369
+ ) -> List[str]:
2370
+ """
2371
+ Get all tags.
2372
+
2373
+ Returns:
2374
+ tags (List[str]): List of tags
2375
+ """
2376
+ return self.server.agent_manager.list_tags(
2377
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
2378
+ cursor=cursor,
2379
+ limit=limit,
2380
+ query_text=query_text,
2381
+ )
2382
+
2383
+ def retrieve_memory(
2384
+ self,
2385
+ agent_id: str,
2386
+ query: str,
2387
+ memory_type: str = "all",
2388
+ search_field: str = "null",
2389
+ search_method: str = "embedding",
2390
+ timezone_str: str = "UTC",
2391
+ limit: int = 10,
2392
+ ) -> dict:
2393
+ """
2394
+ Retrieve memories by searching across different memory types.
2395
+
2396
+ Args:
2397
+ agent_id (str): ID of the agent to retrieve memories for
2398
+ query (str): The keywords/query used to search in the memory
2399
+ memory_type (str): The type of memory to search in. Options: "episodic", "resource", "procedural",
2400
+ "knowledge_vault", "semantic", "all". Defaults to "all".
2401
+ search_field (str): The field to search in the memory. For "episodic": 'summary', 'details';
2402
+ for "resource": 'summary', 'content'; for "procedural": 'summary', 'steps';
2403
+ for "knowledge_vault": 'secret_value', 'caption'; for "semantic": 'name', 'summary', 'details'.
2404
+ Use "null" for default fields. Defaults to "null".
2405
+ search_method (str): The method to search. Options: 'bm25' (keyword-based), 'embedding' (semantic).
2406
+ Defaults to "embedding".
2407
+ timezone_str (str): Timezone string for time-based operations. Defaults to "UTC".
2408
+ limit (int): Maximum number of results to return per memory type. Defaults to 10.
2409
+
2410
+ Returns:
2411
+ dict: Dictionary containing 'results' (list of memories) and 'count' (total number of results)
2412
+ """
2413
+ # Import here to avoid circular imports
2414
+
2415
+ # Validate inputs
2416
+ if (
2417
+ memory_type == "resource"
2418
+ and search_field == "content"
2419
+ and search_method == "embedding"
2420
+ ):
2421
+ raise ValueError(
2422
+ "embedding is not supported for resource memory's 'content' field."
2423
+ )
2424
+ if (
2425
+ memory_type == "knowledge_vault"
2426
+ and search_field == "secret_value"
2427
+ and search_method == "embedding"
2428
+ ):
2429
+ raise ValueError(
2430
+ "embedding is not supported for knowledge_vault memory's 'secret_value' field."
2431
+ )
2432
+
2433
+ # Get the agent to access its memory managers
2434
+ agent_state = self.server.agent_manager.get_agent_by_id(
2435
+ agent_id=agent_id,
2436
+ actor=self.server.user_manager.get_user_by_id(self.user.id),
2437
+ )
2438
+
2439
+ if memory_type == "all":
2440
+ search_field = "null"
2441
+
2442
+ # Initialize result lists
2443
+ formatted_results = []
2444
+
2445
+ # Search episodic memory
2446
+ if memory_type == "episodic" or memory_type == "all":
2447
+ episodic_memory = self.server.episodic_memory_manager.list_episodic_memory(
2448
+ actor=self.user,
2449
+ agent_state=agent_state,
2450
+ query=query,
2451
+ search_field=search_field if search_field != "null" else "summary",
2452
+ search_method=search_method,
2453
+ limit=limit,
2454
+ timezone_str=timezone_str,
2455
+ )
2456
+ formatted_results_episodic = [
2457
+ {
2458
+ "memory_type": "episodic",
2459
+ "id": x.id,
2460
+ "timestamp": x.occurred_at,
2461
+ "event_type": x.event_type,
2462
+ "actor": x.actor,
2463
+ "summary": x.summary,
2464
+ "details": x.details,
2465
+ }
2466
+ for x in episodic_memory
2467
+ ]
2468
+ if memory_type == "episodic":
2469
+ return {
2470
+ "results": formatted_results_episodic,
2471
+ "count": len(formatted_results_episodic),
2472
+ }
2473
+ formatted_results.extend(formatted_results_episodic)
2474
+
2475
+ # Search resource memory
2476
+ if memory_type == "resource" or memory_type == "all":
2477
+ resource_memories = self.server.resource_memory_manager.list_resources(
2478
+ actor=self.user,
2479
+ agent_state=agent_state,
2480
+ query=query,
2481
+ search_field=search_field
2482
+ if search_field != "null"
2483
+ else ("summary" if search_method == "embedding" else "content"),
2484
+ search_method=search_method,
2485
+ limit=limit,
2486
+ timezone_str=timezone_str,
2487
+ )
2488
+ formatted_results_resource = [
2489
+ {
2490
+ "memory_type": "resource",
2491
+ "id": x.id,
2492
+ "resource_type": x.resource_type,
2493
+ "summary": x.summary,
2494
+ "content": x.content,
2495
+ }
2496
+ for x in resource_memories
2497
+ ]
2498
+ if memory_type == "resource":
2499
+ return {
2500
+ "results": formatted_results_resource,
2501
+ "count": len(formatted_results_resource),
2502
+ }
2503
+ formatted_results.extend(formatted_results_resource)
2504
+
2505
+ # Search procedural memory
2506
+ if memory_type == "procedural" or memory_type == "all":
2507
+ procedural_memories = self.server.procedural_memory_manager.list_procedures(
2508
+ actor=self.user,
2509
+ agent_state=agent_state,
2510
+ query=query,
2511
+ search_field=search_field if search_field != "null" else "summary",
2512
+ search_method=search_method,
2513
+ limit=limit,
2514
+ timezone_str=timezone_str,
2515
+ )
2516
+ formatted_results_procedural = [
2517
+ {
2518
+ "memory_type": "procedural",
2519
+ "id": x.id,
2520
+ "entry_type": x.entry_type,
2521
+ "summary": x.summary,
2522
+ "steps": x.steps,
2523
+ }
2524
+ for x in procedural_memories
2525
+ ]
2526
+ if memory_type == "procedural":
2527
+ return {
2528
+ "results": formatted_results_procedural,
2529
+ "count": len(formatted_results_procedural),
2530
+ }
2531
+ formatted_results.extend(formatted_results_procedural)
2532
+
2533
+ # Search knowledge vault
2534
+ if memory_type == "knowledge_vault" or memory_type == "all":
2535
+ knowledge_vault_memories = (
2536
+ self.server.knowledge_vault_manager.list_knowledge(
2537
+ actor=self.user,
2538
+ agent_state=agent_state,
2539
+ query=query,
2540
+ search_field=search_field if search_field != "null" else "caption",
2541
+ search_method=search_method,
2542
+ limit=limit,
2543
+ timezone_str=timezone_str,
2544
+ )
2545
+ )
2546
+ formatted_results_knowledge_vault = [
2547
+ {
2548
+ "memory_type": "knowledge_vault",
2549
+ "id": x.id,
2550
+ "entry_type": x.entry_type,
2551
+ "source": x.source,
2552
+ "sensitivity": x.sensitivity,
2553
+ "secret_value": x.secret_value,
2554
+ "caption": x.caption,
2555
+ }
2556
+ for x in knowledge_vault_memories
2557
+ ]
2558
+ if memory_type == "knowledge_vault":
2559
+ return {
2560
+ "results": formatted_results_knowledge_vault,
2561
+ "count": len(formatted_results_knowledge_vault),
2562
+ }
2563
+ formatted_results.extend(formatted_results_knowledge_vault)
2564
+
2565
+ # Search semantic memory
2566
+ if memory_type == "semantic" or memory_type == "all":
2567
+ semantic_memories = self.server.semantic_memory_manager.list_semantic_items(
2568
+ actor=self.user,
2569
+ agent_state=agent_state,
2570
+ query=query,
2571
+ search_field=search_field if search_field != "null" else "summary",
2572
+ search_method=search_method,
2573
+ limit=limit,
2574
+ timezone_str=timezone_str,
2575
+ )
2576
+ formatted_results_semantic = [
2577
+ {
2578
+ "memory_type": "semantic",
2579
+ "id": x.id,
2580
+ "name": x.name,
2581
+ "summary": x.summary,
2582
+ "details": x.details,
2583
+ "source": x.source,
2584
+ }
2585
+ for x in semantic_memories
2586
+ ]
2587
+ if memory_type == "semantic":
2588
+ return {
2589
+ "results": formatted_results_semantic,
2590
+ "count": len(formatted_results_semantic),
2591
+ }
2592
+ formatted_results.extend(formatted_results_semantic)
2593
+
2594
+ return {"results": formatted_results, "count": len(formatted_results)}