letta-nightly 0.5.5.dev20241122170833__py3-none-any.whl → 0.6.0.dev20241204051808__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of letta-nightly might be problematic. Click here for more details.

Files changed (70) hide show
  1. letta/__init__.py +2 -2
  2. letta/agent.py +155 -166
  3. letta/agent_store/chroma.py +2 -0
  4. letta/agent_store/db.py +1 -1
  5. letta/cli/cli.py +12 -8
  6. letta/cli/cli_config.py +1 -1
  7. letta/client/client.py +765 -137
  8. letta/config.py +2 -2
  9. letta/constants.py +10 -14
  10. letta/errors.py +12 -0
  11. letta/functions/function_sets/base.py +38 -1
  12. letta/functions/functions.py +40 -57
  13. letta/functions/helpers.py +0 -4
  14. letta/functions/schema_generator.py +279 -18
  15. letta/helpers/tool_rule_solver.py +6 -5
  16. letta/llm_api/helpers.py +99 -5
  17. letta/llm_api/openai.py +8 -2
  18. letta/local_llm/utils.py +13 -6
  19. letta/log.py +7 -9
  20. letta/main.py +1 -1
  21. letta/metadata.py +53 -38
  22. letta/o1_agent.py +1 -4
  23. letta/orm/__init__.py +2 -0
  24. letta/orm/block.py +7 -3
  25. letta/orm/blocks_agents.py +32 -0
  26. letta/orm/errors.py +8 -0
  27. letta/orm/mixins.py +8 -0
  28. letta/orm/organization.py +8 -1
  29. letta/orm/sandbox_config.py +56 -0
  30. letta/orm/sqlalchemy_base.py +68 -10
  31. letta/persistence_manager.py +1 -0
  32. letta/schemas/agent.py +57 -52
  33. letta/schemas/block.py +85 -26
  34. letta/schemas/blocks_agents.py +32 -0
  35. letta/schemas/enums.py +14 -0
  36. letta/schemas/letta_base.py +10 -1
  37. letta/schemas/letta_request.py +11 -23
  38. letta/schemas/letta_response.py +1 -2
  39. letta/schemas/memory.py +41 -76
  40. letta/schemas/message.py +3 -3
  41. letta/schemas/sandbox_config.py +114 -0
  42. letta/schemas/tool.py +37 -1
  43. letta/schemas/tool_rule.py +13 -5
  44. letta/server/rest_api/app.py +5 -4
  45. letta/server/rest_api/interface.py +12 -19
  46. letta/server/rest_api/routers/openai/assistants/threads.py +2 -3
  47. letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +0 -2
  48. letta/server/rest_api/routers/v1/__init__.py +4 -9
  49. letta/server/rest_api/routers/v1/agents.py +145 -61
  50. letta/server/rest_api/routers/v1/blocks.py +50 -5
  51. letta/server/rest_api/routers/v1/sandbox_configs.py +127 -0
  52. letta/server/rest_api/routers/v1/sources.py +8 -1
  53. letta/server/rest_api/routers/v1/tools.py +139 -13
  54. letta/server/rest_api/utils.py +6 -0
  55. letta/server/server.py +397 -340
  56. letta/server/static_files/assets/index-9fa459a2.js +1 -1
  57. letta/services/block_manager.py +23 -2
  58. letta/services/blocks_agents_manager.py +106 -0
  59. letta/services/per_agent_lock_manager.py +18 -0
  60. letta/services/sandbox_config_manager.py +256 -0
  61. letta/services/tool_execution_sandbox.py +352 -0
  62. letta/services/tool_manager.py +16 -22
  63. letta/services/tool_sandbox_env/.gitkeep +0 -0
  64. letta/settings.py +4 -0
  65. letta/utils.py +0 -7
  66. {letta_nightly-0.5.5.dev20241122170833.dist-info → letta_nightly-0.6.0.dev20241204051808.dist-info}/METADATA +8 -6
  67. {letta_nightly-0.5.5.dev20241122170833.dist-info → letta_nightly-0.6.0.dev20241204051808.dist-info}/RECORD +70 -60
  68. {letta_nightly-0.5.5.dev20241122170833.dist-info → letta_nightly-0.6.0.dev20241204051808.dist-info}/LICENSE +0 -0
  69. {letta_nightly-0.5.5.dev20241122170833.dist-info → letta_nightly-0.6.0.dev20241204051808.dist-info}/WHEEL +0 -0
  70. {letta_nightly-0.5.5.dev20241122170833.dist-info → letta_nightly-0.6.0.dev20241204051808.dist-info}/entry_points.txt +0 -0
letta/client/client.py CHANGED
@@ -5,20 +5,17 @@ from typing import Callable, Dict, Generator, List, Optional, Union
5
5
  import requests
6
6
 
7
7
  import letta.utils
8
- from letta.constants import ADMIN_PREFIX, BASE_TOOLS, DEFAULT_HUMAN, DEFAULT_PERSONA
8
+ from letta.constants import (
9
+ ADMIN_PREFIX,
10
+ BASE_MEMORY_TOOLS,
11
+ BASE_TOOLS,
12
+ DEFAULT_HUMAN,
13
+ DEFAULT_PERSONA,
14
+ )
9
15
  from letta.data_sources.connectors import DataConnector
10
16
  from letta.functions.functions import parse_source_code
11
- from letta.memory import get_memory_functions
12
17
  from letta.schemas.agent import AgentState, AgentType, CreateAgent, UpdateAgentState
13
- from letta.schemas.block import (
14
- Block,
15
- BlockCreate,
16
- BlockUpdate,
17
- Human,
18
- Persona,
19
- UpdateHuman,
20
- UpdatePersona,
21
- )
18
+ from letta.schemas.block import Block, BlockUpdate, CreateBlock, Human, Persona
22
19
  from letta.schemas.embedding_config import EmbeddingConfig
23
20
 
24
21
  # new schemas
@@ -39,6 +36,16 @@ from letta.schemas.message import Message, MessageCreate, UpdateMessage
39
36
  from letta.schemas.openai.chat_completions import ToolCall
40
37
  from letta.schemas.organization import Organization
41
38
  from letta.schemas.passage import Passage
39
+ from letta.schemas.sandbox_config import (
40
+ E2BSandboxConfig,
41
+ LocalSandboxConfig,
42
+ SandboxConfig,
43
+ SandboxConfigCreate,
44
+ SandboxConfigUpdate,
45
+ SandboxEnvironmentVariable,
46
+ SandboxEnvironmentVariableCreate,
47
+ SandboxEnvironmentVariableUpdate,
48
+ )
42
49
  from letta.schemas.source import Source, SourceCreate, SourceUpdate
43
50
  from letta.schemas.tool import Tool, ToolCreate, ToolUpdate
44
51
  from letta.schemas.tool_rule import BaseToolRule
@@ -72,7 +79,7 @@ class AbstractClient(object):
72
79
  agent_type: Optional[AgentType] = AgentType.memgpt_agent,
73
80
  embedding_config: Optional[EmbeddingConfig] = None,
74
81
  llm_config: Optional[LLMConfig] = None,
75
- memory: Memory = ChatMemory(human=get_human_text(DEFAULT_HUMAN), persona=get_persona_text(DEFAULT_PERSONA)),
82
+ memory=None,
76
83
  system: Optional[str] = None,
77
84
  tools: Optional[List[str]] = None,
78
85
  tool_rules: Optional[List[BaseToolRule]] = None,
@@ -144,11 +151,10 @@ class AbstractClient(object):
144
151
  stream: Optional[bool] = False,
145
152
  stream_steps: bool = False,
146
153
  stream_tokens: bool = False,
147
- include_full_message: Optional[bool] = False,
148
154
  ) -> LettaResponse:
149
155
  raise NotImplementedError
150
156
 
151
- def user_message(self, agent_id: str, message: str, include_full_message: Optional[bool] = False) -> LettaResponse:
157
+ def user_message(self, agent_id: str, message: str) -> LettaResponse:
152
158
  raise NotImplementedError
153
159
 
154
160
  def create_human(self, name: str, text: str) -> Human:
@@ -201,6 +207,14 @@ class AbstractClient(object):
201
207
  ) -> Tool:
202
208
  raise NotImplementedError
203
209
 
210
+ def create_or_update_tool(
211
+ self,
212
+ func,
213
+ name: Optional[str] = None,
214
+ tags: Optional[List[str]] = None,
215
+ ) -> Tool:
216
+ raise NotImplementedError
217
+
204
218
  def update_tool(
205
219
  self,
206
220
  id: str,
@@ -296,6 +310,112 @@ class AbstractClient(object):
296
310
  def delete_org(self, org_id: str) -> Organization:
297
311
  raise NotImplementedError
298
312
 
313
+ def create_sandbox_config(self, config: Union[LocalSandboxConfig, E2BSandboxConfig]) -> 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(self, sandbox_config_id: str, config: Union[LocalSandboxConfig, E2BSandboxConfig]) -> SandboxConfig:
326
+ """
327
+ Update an existing sandbox configuration.
328
+
329
+ Args:
330
+ sandbox_config_id (str): The ID of the sandbox configuration to update.
331
+ config (Union[LocalSandboxConfig, E2BSandboxConfig]): The updated sandbox settings.
332
+
333
+ Returns:
334
+ SandboxConfig: The updated sandbox configuration.
335
+ """
336
+ raise NotImplementedError
337
+
338
+ def delete_sandbox_config(self, sandbox_config_id: str) -> None:
339
+ """
340
+ Delete a sandbox configuration.
341
+
342
+ Args:
343
+ sandbox_config_id (str): The ID of the sandbox configuration to delete.
344
+ """
345
+ raise NotImplementedError
346
+
347
+ def list_sandbox_configs(self, limit: int = 50, cursor: Optional[str] = None) -> List[SandboxConfig]:
348
+ """
349
+ List all sandbox configurations.
350
+
351
+ Args:
352
+ limit (int, optional): The maximum number of sandbox configurations to return. Defaults to 50.
353
+ cursor (Optional[str], optional): The pagination cursor for retrieving the next set of results.
354
+
355
+ Returns:
356
+ List[SandboxConfig]: A list of sandbox configurations.
357
+ """
358
+ raise NotImplementedError
359
+
360
+ def create_sandbox_env_var(
361
+ self, sandbox_config_id: str, key: str, value: str, description: Optional[str] = None
362
+ ) -> SandboxEnvironmentVariable:
363
+ """
364
+ Create a new environment variable for a sandbox configuration.
365
+
366
+ Args:
367
+ sandbox_config_id (str): The ID of the sandbox configuration to associate the environment variable with.
368
+ key (str): The name of the environment variable.
369
+ value (str): The value of the environment variable.
370
+ description (Optional[str], optional): A description of the environment variable. Defaults to None.
371
+
372
+ Returns:
373
+ SandboxEnvironmentVariable: The created environment variable.
374
+ """
375
+ raise NotImplementedError
376
+
377
+ def update_sandbox_env_var(
378
+ self, env_var_id: str, key: Optional[str] = None, value: Optional[str] = None, description: Optional[str] = None
379
+ ) -> SandboxEnvironmentVariable:
380
+ """
381
+ Update an existing environment variable.
382
+
383
+ Args:
384
+ env_var_id (str): The ID of the environment variable to update.
385
+ key (Optional[str], optional): The updated name of the environment variable. Defaults to None.
386
+ value (Optional[str], optional): The updated value of the environment variable. Defaults to None.
387
+ description (Optional[str], optional): The updated description of the environment variable. Defaults to None.
388
+
389
+ Returns:
390
+ SandboxEnvironmentVariable: The updated environment variable.
391
+ """
392
+ raise NotImplementedError
393
+
394
+ def delete_sandbox_env_var(self, env_var_id: str) -> None:
395
+ """
396
+ Delete an environment variable by its ID.
397
+
398
+ Args:
399
+ env_var_id (str): The ID of the environment variable to delete.
400
+ """
401
+ raise NotImplementedError
402
+
403
+ def list_sandbox_env_vars(
404
+ self, sandbox_config_id: str, limit: int = 50, cursor: Optional[str] = None
405
+ ) -> List[SandboxEnvironmentVariable]:
406
+ """
407
+ List all environment variables associated with a sandbox configuration.
408
+
409
+ Args:
410
+ sandbox_config_id (str): The ID of the sandbox configuration to retrieve environment variables for.
411
+ limit (int, optional): The maximum number of environment variables to return. Defaults to 50.
412
+ cursor (Optional[str], optional): The pagination cursor for retrieving the next set of results.
413
+
414
+ Returns:
415
+ List[SandboxEnvironmentVariable]: A list of environment variables.
416
+ """
417
+ raise NotImplementedError
418
+
299
419
 
300
420
  class RESTClient(AbstractClient):
301
421
  """
@@ -314,6 +434,7 @@ class RESTClient(AbstractClient):
314
434
  debug: bool = False,
315
435
  default_llm_config: Optional[LLMConfig] = None,
316
436
  default_embedding_config: Optional[EmbeddingConfig] = None,
437
+ headers: Optional[Dict] = None,
317
438
  ):
318
439
  """
319
440
  Initializes a new instance of Client class.
@@ -322,12 +443,16 @@ class RESTClient(AbstractClient):
322
443
  auto_save (bool): Whether to automatically save changes.
323
444
  user_id (str): The user ID.
324
445
  debug (bool): Whether to print debug information.
325
- default
446
+ default_llm_config (Optional[LLMConfig]): The default LLM configuration.
447
+ default_embedding_config (Optional[EmbeddingConfig]): The default embedding configuration.
448
+ headers (Optional[Dict]): The additional headers for the REST API.
326
449
  """
327
450
  super().__init__(debug=debug)
328
451
  self.base_url = base_url
329
452
  self.api_prefix = api_prefix
330
453
  self.headers = {"accept": "application/json", "authorization": f"Bearer {token}"}
454
+ if headers:
455
+ self.headers.update(headers)
331
456
  self._default_llm_config = default_llm_config
332
457
  self._default_embedding_config = default_embedding_config
333
458
 
@@ -399,27 +524,13 @@ class RESTClient(AbstractClient):
399
524
  Returns:
400
525
  agent_state (AgentState): State of the created agent
401
526
  """
402
-
403
- # TODO: implement this check once name lookup works
404
- # if name:
405
- # exist_agent_id = self.get_agent_id(agent_name=name)
406
-
407
- # raise ValueError(f"Agent with name {name} already exists")
408
-
409
- # construct list of tools
410
527
  tool_names = []
411
528
  if tools:
412
529
  tool_names += tools
413
530
  if include_base_tools:
414
531
  tool_names += BASE_TOOLS
532
+ tool_names += BASE_MEMORY_TOOLS
415
533
 
416
- # add memory tools
417
- memory_functions = get_memory_functions(memory)
418
- for func_name, func in memory_functions.items():
419
- tool = self.create_tool(func, name=func_name, tags=["memory", "letta-base"])
420
- tool_names.append(tool.name)
421
-
422
- # check if default configs are provided
423
534
  assert embedding_config or self._default_embedding_config, f"Embedding config must be provided"
424
535
  assert llm_config or self._default_llm_config, f"LLM config must be provided"
425
536
 
@@ -428,7 +539,7 @@ class RESTClient(AbstractClient):
428
539
  name=name,
429
540
  description=description,
430
541
  metadata_=metadata,
431
- memory=memory,
542
+ memory_blocks=[],
432
543
  tools=tool_names,
433
544
  tool_rules=tool_rules,
434
545
  system=system,
@@ -450,7 +561,20 @@ class RESTClient(AbstractClient):
450
561
 
451
562
  if response.status_code != 200:
452
563
  raise ValueError(f"Status {response.status_code} - Failed to create agent: {response.text}")
453
- return AgentState(**response.json())
564
+
565
+ # gather agent state
566
+ agent_state = AgentState(**response.json())
567
+
568
+ # create and link blocks
569
+ for block in memory.get_blocks():
570
+ if not self.get_block(block.id):
571
+ # note: this does not update existing blocks
572
+ # WARNING: this resets the block ID - this method is a hack for backwards compat, should eventually use CreateBlock not Memory
573
+ block = self.create_block(label=block.label, value=block.value, limit=block.limit)
574
+ self.link_agent_memory_block(agent_id=agent_state.id, block_id=block.id)
575
+
576
+ # refresh and return agent
577
+ return self.get_agent(agent_state.id)
454
578
 
455
579
  def update_message(
456
580
  self,
@@ -483,12 +607,11 @@ class RESTClient(AbstractClient):
483
607
  name: Optional[str] = None,
484
608
  description: Optional[str] = None,
485
609
  system: Optional[str] = None,
486
- tools: Optional[List[str]] = None,
610
+ tool_names: Optional[List[str]] = None,
487
611
  metadata: Optional[Dict] = None,
488
612
  llm_config: Optional[LLMConfig] = None,
489
613
  embedding_config: Optional[EmbeddingConfig] = None,
490
614
  message_ids: Optional[List[str]] = None,
491
- memory: Optional[Memory] = None,
492
615
  tags: Optional[List[str]] = None,
493
616
  ):
494
617
  """
@@ -499,12 +622,11 @@ class RESTClient(AbstractClient):
499
622
  name (str): Name of the agent
500
623
  description (str): Description of the agent
501
624
  system (str): System configuration
502
- tools (List[str]): List of tools
625
+ tool_names (List[str]): List of tools
503
626
  metadata (Dict): Metadata
504
627
  llm_config (LLMConfig): LLM configuration
505
628
  embedding_config (EmbeddingConfig): Embedding configuration
506
629
  message_ids (List[str]): List of message IDs
507
- memory (Memory): Memory configuration
508
630
  tags (List[str]): Tags for filtering agents
509
631
 
510
632
  Returns:
@@ -514,14 +636,13 @@ class RESTClient(AbstractClient):
514
636
  id=agent_id,
515
637
  name=name,
516
638
  system=system,
517
- tools=tools,
639
+ tool_names=tool_names,
518
640
  tags=tags,
519
641
  description=description,
520
642
  metadata_=metadata,
521
643
  llm_config=llm_config,
522
644
  embedding_config=embedding_config,
523
645
  message_ids=message_ids,
524
- memory=memory,
525
646
  )
526
647
  response = requests.patch(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}", json=request.model_dump(), headers=self.headers)
527
648
  if response.status_code != 200:
@@ -715,7 +836,7 @@ class RESTClient(AbstractClient):
715
836
 
716
837
  # agent interactions
717
838
 
718
- def user_message(self, agent_id: str, message: str, include_full_message: Optional[bool] = False) -> LettaResponse:
839
+ def user_message(self, agent_id: str, message: str) -> LettaResponse:
719
840
  """
720
841
  Send a message to an agent as a user
721
842
 
@@ -726,7 +847,7 @@ class RESTClient(AbstractClient):
726
847
  Returns:
727
848
  response (LettaResponse): Response from the agent
728
849
  """
729
- return self.send_message(agent_id, message, role="user", include_full_message=include_full_message)
850
+ return self.send_message(agent_id=agent_id, message=message, role="user")
730
851
 
731
852
  def save(self):
732
853
  raise NotImplementedError
@@ -813,13 +934,13 @@ class RESTClient(AbstractClient):
813
934
 
814
935
  def send_message(
815
936
  self,
816
- agent_id: str,
817
937
  message: str,
818
938
  role: str,
939
+ agent_id: Optional[str] = None,
819
940
  name: Optional[str] = None,
941
+ stream: Optional[bool] = False,
820
942
  stream_steps: bool = False,
821
943
  stream_tokens: bool = False,
822
- include_full_message: bool = False,
823
944
  ) -> Union[LettaResponse, Generator[LettaStreamingResponse, None, None]]:
824
945
  """
825
946
  Send a message to an agent
@@ -840,17 +961,11 @@ class RESTClient(AbstractClient):
840
961
  # TODO: figure out how to handle stream_steps and stream_tokens
841
962
 
842
963
  # When streaming steps is True, stream_tokens must be False
843
- request = LettaRequest(
844
- messages=messages,
845
- stream_steps=stream_steps,
846
- stream_tokens=stream_tokens,
847
- return_message_object=include_full_message,
848
- )
964
+ request = LettaRequest(messages=messages)
849
965
  if stream_tokens or stream_steps:
850
966
  from letta.client.streaming import _sse_post
851
967
 
852
- request.return_message_object = False
853
- return _sse_post(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/messages", request.model_dump(), self.headers)
968
+ return _sse_post(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/messages/stream", request.model_dump(), self.headers)
854
969
  else:
855
970
  response = requests.post(
856
971
  f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/messages", json=request.model_dump(), headers=self.headers
@@ -884,8 +999,12 @@ class RESTClient(AbstractClient):
884
999
  else:
885
1000
  return [Block(**block) for block in response.json()]
886
1001
 
887
- def create_block(self, label: str, value: str, template_name: Optional[str] = None, is_template: bool = False) -> Block: #
888
- request = BlockCreate(label=label, value=value, template=is_template, template_name=template_name)
1002
+ def create_block(
1003
+ self, label: str, value: str, limit: Optional[int] = None, template_name: Optional[str] = None, is_template: bool = False
1004
+ ) -> Block: #
1005
+ request = CreateBlock(label=label, value=value, template=is_template, template_name=template_name)
1006
+ if limit:
1007
+ request.limit = limit
889
1008
  response = requests.post(f"{self.base_url}/{self.api_prefix}/blocks", json=request.model_dump(), headers=self.headers)
890
1009
  if response.status_code != 200:
891
1010
  raise ValueError(f"Failed to create block: {response.text}")
@@ -1324,18 +1443,39 @@ class RESTClient(AbstractClient):
1324
1443
  Returns:
1325
1444
  tool (Tool): The created tool.
1326
1445
  """
1446
+ source_code = parse_source_code(func)
1447
+ source_type = "python"
1327
1448
 
1328
- # TODO: check tool update code
1329
- # TODO: check if tool already exists
1449
+ # call server function
1450
+ request = ToolCreate(source_type=source_type, source_code=source_code, name=name, tags=tags)
1451
+ response = requests.post(f"{self.base_url}/{self.api_prefix}/tools", json=request.model_dump(), headers=self.headers)
1452
+ if response.status_code != 200:
1453
+ raise ValueError(f"Failed to create tool: {response.text}")
1454
+ return Tool(**response.json())
1330
1455
 
1331
- # TODO: how to load modules?
1332
- # parse source code/schema
1456
+ def create_or_update_tool(
1457
+ self,
1458
+ func: Callable,
1459
+ name: Optional[str] = None,
1460
+ tags: Optional[List[str]] = None,
1461
+ ) -> Tool:
1462
+ """
1463
+ 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.
1464
+
1465
+ Args:
1466
+ func (callable): The function to create a tool for.
1467
+ name: (str): Name of the tool (must be unique per-user.)
1468
+ tags (Optional[List[str]], optional): Tags for the tool. Defaults to None.
1469
+
1470
+ Returns:
1471
+ tool (Tool): The created tool.
1472
+ """
1333
1473
  source_code = parse_source_code(func)
1334
1474
  source_type = "python"
1335
1475
 
1336
1476
  # call server function
1337
1477
  request = ToolCreate(source_type=source_type, source_code=source_code, name=name, tags=tags)
1338
- response = requests.post(f"{self.base_url}/{self.api_prefix}/tools", json=request.model_dump(), headers=self.headers)
1478
+ response = requests.put(f"{self.base_url}/{self.api_prefix}/tools", json=request.model_dump(), headers=self.headers)
1339
1479
  if response.status_code != 200:
1340
1480
  raise ValueError(f"Failed to create tool: {response.text}")
1341
1481
  return Tool(**response.json())
@@ -1373,45 +1513,6 @@ class RESTClient(AbstractClient):
1373
1513
  raise ValueError(f"Failed to update tool: {response.text}")
1374
1514
  return Tool(**response.json())
1375
1515
 
1376
- # def create_tool(
1377
- # self,
1378
- # func,
1379
- # name: Optional[str] = None,
1380
- # update: Optional[bool] = True, # TODO: actually use this
1381
- # tags: Optional[List[str]] = None,
1382
- # ):
1383
- # """Create a tool
1384
-
1385
- # Args:
1386
- # func (callable): The function to create a tool for.
1387
- # tags (Optional[List[str]], optional): Tags for the tool. Defaults to None.
1388
- # update (bool, optional): Update the tool if it already exists. Defaults to True.
1389
-
1390
- # Returns:
1391
- # Tool object
1392
- # """
1393
-
1394
- # # TODO: check if tool already exists
1395
- # # TODO: how to load modules?
1396
- # # parse source code/schema
1397
- # source_code = parse_source_code(func)
1398
- # json_schema = generate_schema(func, name)
1399
- # source_type = "python"
1400
- # json_schema["name"]
1401
-
1402
- # # create data
1403
- # data = {"source_code": source_code, "source_type": source_type, "tags": tags, "json_schema": json_schema, "update": update}
1404
- # try:
1405
- # CreateToolRequest(**data) # validate data
1406
- # except Exception as e:
1407
- # raise ValueError(f"Failed to create tool: {e}, invalid input {data}")
1408
-
1409
- # # make REST request
1410
- # response = requests.post(f"{self.base_url}/{self.api_prefix}/tools", json=data, headers=self.headers)
1411
- # if response.status_code != 200:
1412
- # raise ValueError(f"Failed to create tool: {response.text}")
1413
- # return ToolModel(**response.json())
1414
-
1415
1516
  def list_tools(self, cursor: Optional[str] = None, limit: Optional[int] = 50) -> List[Tool]:
1416
1517
  """
1417
1518
  List available tools for the user.
@@ -1565,6 +1666,292 @@ class RESTClient(AbstractClient):
1565
1666
  # Parse and return the deleted organization
1566
1667
  return Organization(**response.json())
1567
1668
 
1669
+ def create_sandbox_config(self, config: Union[LocalSandboxConfig, E2BSandboxConfig]) -> SandboxConfig:
1670
+ """
1671
+ Create a new sandbox configuration.
1672
+ """
1673
+ payload = {
1674
+ "config": config.model_dump(),
1675
+ }
1676
+ response = requests.post(f"{self.base_url}/{self.api_prefix}/sandbox-config", headers=self.headers, json=payload)
1677
+ if response.status_code != 200:
1678
+ raise ValueError(f"Failed to create sandbox config: {response.text}")
1679
+ return SandboxConfig(**response.json())
1680
+
1681
+ def update_sandbox_config(self, sandbox_config_id: str, config: Union[LocalSandboxConfig, E2BSandboxConfig]) -> SandboxConfig:
1682
+ """
1683
+ Update an existing sandbox configuration.
1684
+ """
1685
+ payload = {
1686
+ "config": config.model_dump(),
1687
+ }
1688
+ response = requests.patch(
1689
+ f"{self.base_url}/{self.api_prefix}/sandbox-config/{sandbox_config_id}",
1690
+ headers=self.headers,
1691
+ json=payload,
1692
+ )
1693
+ if response.status_code != 200:
1694
+ raise ValueError(f"Failed to update sandbox config with ID '{sandbox_config_id}': {response.text}")
1695
+ return SandboxConfig(**response.json())
1696
+
1697
+ def delete_sandbox_config(self, sandbox_config_id: str) -> None:
1698
+ """
1699
+ Delete a sandbox configuration.
1700
+ """
1701
+ response = requests.delete(f"{self.base_url}/{self.api_prefix}/sandbox-config/{sandbox_config_id}", headers=self.headers)
1702
+ if response.status_code == 404:
1703
+ raise ValueError(f"Sandbox config with ID '{sandbox_config_id}' does not exist")
1704
+ elif response.status_code != 204:
1705
+ raise ValueError(f"Failed to delete sandbox config with ID '{sandbox_config_id}': {response.text}")
1706
+
1707
+ def list_sandbox_configs(self, limit: int = 50, cursor: Optional[str] = None) -> List[SandboxConfig]:
1708
+ """
1709
+ List all sandbox configurations.
1710
+ """
1711
+ params = {"limit": limit, "cursor": cursor}
1712
+ response = requests.get(f"{self.base_url}/{self.api_prefix}/sandbox-config", headers=self.headers, params=params)
1713
+ if response.status_code != 200:
1714
+ raise ValueError(f"Failed to list sandbox configs: {response.text}")
1715
+ return [SandboxConfig(**config_data) for config_data in response.json()]
1716
+
1717
+ def create_sandbox_env_var(
1718
+ self, sandbox_config_id: str, key: str, value: str, description: Optional[str] = None
1719
+ ) -> SandboxEnvironmentVariable:
1720
+ """
1721
+ Create a new environment variable for a sandbox configuration.
1722
+ """
1723
+ payload = {"key": key, "value": value, "description": description}
1724
+ response = requests.post(
1725
+ f"{self.base_url}/{self.api_prefix}/sandbox-config/{sandbox_config_id}/environment-variable",
1726
+ headers=self.headers,
1727
+ json=payload,
1728
+ )
1729
+ if response.status_code != 200:
1730
+ raise ValueError(f"Failed to create environment variable for sandbox config ID '{sandbox_config_id}': {response.text}")
1731
+ return SandboxEnvironmentVariable(**response.json())
1732
+
1733
+ def update_sandbox_env_var(
1734
+ self, env_var_id: str, key: Optional[str] = None, value: Optional[str] = None, description: Optional[str] = None
1735
+ ) -> SandboxEnvironmentVariable:
1736
+ """
1737
+ Update an existing environment variable.
1738
+ """
1739
+ payload = {k: v for k, v in {"key": key, "value": value, "description": description}.items() if v is not None}
1740
+ response = requests.patch(
1741
+ f"{self.base_url}/{self.api_prefix}/sandbox-config/environment-variable/{env_var_id}",
1742
+ headers=self.headers,
1743
+ json=payload,
1744
+ )
1745
+ if response.status_code != 200:
1746
+ raise ValueError(f"Failed to update environment variable with ID '{env_var_id}': {response.text}")
1747
+ return SandboxEnvironmentVariable(**response.json())
1748
+
1749
+ def delete_sandbox_env_var(self, env_var_id: str) -> None:
1750
+ """
1751
+ Delete an environment variable by its ID.
1752
+ """
1753
+ response = requests.delete(
1754
+ f"{self.base_url}/{self.api_prefix}/sandbox-config/environment-variable/{env_var_id}", headers=self.headers
1755
+ )
1756
+ if response.status_code == 404:
1757
+ raise ValueError(f"Environment variable with ID '{env_var_id}' does not exist")
1758
+ elif response.status_code != 204:
1759
+ raise ValueError(f"Failed to delete environment variable with ID '{env_var_id}': {response.text}")
1760
+
1761
+ def list_sandbox_env_vars(
1762
+ self, sandbox_config_id: str, limit: int = 50, cursor: Optional[str] = None
1763
+ ) -> List[SandboxEnvironmentVariable]:
1764
+ """
1765
+ List all environment variables associated with a sandbox configuration.
1766
+ """
1767
+ params = {"limit": limit, "cursor": cursor}
1768
+ response = requests.get(
1769
+ f"{self.base_url}/{self.api_prefix}/sandbox-config/{sandbox_config_id}/environment-variable",
1770
+ headers=self.headers,
1771
+ params=params,
1772
+ )
1773
+ if response.status_code != 200:
1774
+ raise ValueError(f"Failed to list environment variables for sandbox config ID '{sandbox_config_id}': {response.text}")
1775
+ return [SandboxEnvironmentVariable(**var_data) for var_data in response.json()]
1776
+
1777
+ def update_agent_memory_block_label(self, agent_id: str, current_label: str, new_label: str) -> Memory:
1778
+ """Rename a block in the agent's core memory
1779
+
1780
+ Args:
1781
+ agent_id (str): The agent ID
1782
+ current_label (str): The current label of the block
1783
+ new_label (str): The new label of the block
1784
+
1785
+ Returns:
1786
+ memory (Memory): The updated memory
1787
+ """
1788
+ block = self.get_agent_memory_block(agent_id, current_label)
1789
+ return self.update_block(block.id, label=new_label)
1790
+
1791
+ # TODO: remove this
1792
+ def add_agent_memory_block(self, agent_id: str, create_block: CreateBlock) -> Memory:
1793
+ """
1794
+ Create and link a memory block to an agent's core memory
1795
+
1796
+ Args:
1797
+ agent_id (str): The agent ID
1798
+ create_block (CreateBlock): The block to create
1799
+
1800
+ Returns:
1801
+ memory (Memory): The updated memory
1802
+ """
1803
+ response = requests.post(
1804
+ f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/memory/block",
1805
+ headers=self.headers,
1806
+ json=create_block.model_dump(),
1807
+ )
1808
+ if response.status_code != 200:
1809
+ raise ValueError(f"Failed to add agent memory block: {response.text}")
1810
+ return Memory(**response.json())
1811
+
1812
+ def link_agent_memory_block(self, agent_id: str, block_id: str) -> Memory:
1813
+ """
1814
+ Link a block to an agent's core memory
1815
+
1816
+ Args:
1817
+ agent_id (str): The agent ID
1818
+ block_id (str): The block ID
1819
+
1820
+ Returns:
1821
+ memory (Memory): The updated memory
1822
+ """
1823
+ params = {"agent_id": agent_id}
1824
+ response = requests.patch(
1825
+ f"{self.base_url}/{self.api_prefix}/blocks/{block_id}/attach",
1826
+ params=params,
1827
+ headers=self.headers,
1828
+ )
1829
+ if response.status_code != 200:
1830
+ raise ValueError(f"Failed to link agent memory block: {response.text}")
1831
+ return Block(**response.json())
1832
+
1833
+ def remove_agent_memory_block(self, agent_id: str, block_label: str) -> Memory:
1834
+ """
1835
+ Unlike a block from the agent's core memory
1836
+
1837
+ Args:
1838
+ agent_id (str): The agent ID
1839
+ block_label (str): The block label
1840
+
1841
+ Returns:
1842
+ memory (Memory): The updated memory
1843
+ """
1844
+ response = requests.delete(
1845
+ f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/memory/block/{block_label}",
1846
+ headers=self.headers,
1847
+ )
1848
+ if response.status_code != 200:
1849
+ raise ValueError(f"Failed to remove agent memory block: {response.text}")
1850
+ return Memory(**response.json())
1851
+
1852
+ def get_agent_memory_blocks(self, agent_id: str) -> List[Block]:
1853
+ """
1854
+ Get all the blocks in the agent's core memory
1855
+
1856
+ Args:
1857
+ agent_id (str): The agent ID
1858
+
1859
+ Returns:
1860
+ blocks (List[Block]): The blocks in the agent's core memory
1861
+ """
1862
+ response = requests.get(f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/memory/block", headers=self.headers)
1863
+ if response.status_code != 200:
1864
+ raise ValueError(f"Failed to get agent memory blocks: {response.text}")
1865
+ return [Block(**block) for block in response.json()]
1866
+
1867
+ def get_agent_memory_block(self, agent_id: str, label: str) -> Block:
1868
+ """
1869
+ Get a block in the agent's core memory by its label
1870
+
1871
+ Args:
1872
+ agent_id (str): The agent ID
1873
+ label (str): The label in the agent's core memory
1874
+
1875
+ Returns:
1876
+ block (Block): The block corresponding to the label
1877
+ """
1878
+ response = requests.get(
1879
+ f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/memory/block/{label}",
1880
+ headers=self.headers,
1881
+ )
1882
+ if response.status_code != 200:
1883
+ raise ValueError(f"Failed to get agent memory block: {response.text}")
1884
+ return Block(**response.json())
1885
+
1886
+ def update_agent_memory_block(
1887
+ self,
1888
+ agent_id: str,
1889
+ label: str,
1890
+ value: Optional[str] = None,
1891
+ limit: Optional[int] = None,
1892
+ ):
1893
+ """
1894
+ Update a block in the agent's core memory by specifying its label
1895
+
1896
+ Args:
1897
+ agent_id (str): The agent ID
1898
+ label (str): The label of the block
1899
+ value (str): The new value of the block
1900
+ limit (int): The new limit of the block
1901
+
1902
+ Returns:
1903
+ block (Block): The updated block
1904
+ """
1905
+ # setup data
1906
+ data = {}
1907
+ if value:
1908
+ data["value"] = value
1909
+ if limit:
1910
+ data["limit"] = limit
1911
+ response = requests.patch(
1912
+ f"{self.base_url}/{self.api_prefix}/agents/{agent_id}/memory/block/{label}",
1913
+ headers=self.headers,
1914
+ json=data,
1915
+ )
1916
+ if response.status_code != 200:
1917
+ raise ValueError(f"Failed to update agent memory block: {response.text}")
1918
+ return Block(**response.json())
1919
+
1920
+ def update_block(
1921
+ self,
1922
+ block_id: str,
1923
+ label: Optional[str] = None,
1924
+ value: Optional[str] = None,
1925
+ limit: Optional[int] = None,
1926
+ ):
1927
+ """
1928
+ Update a block given the ID with the provided fields
1929
+
1930
+ Args:
1931
+ block_id (str): ID of the block
1932
+ label (str): Label to assign to the block
1933
+ value (str): Value to assign to the block
1934
+ limit (int): Token limit to assign to the block
1935
+
1936
+ Returns:
1937
+ block (Block): Updated block
1938
+ """
1939
+ data = {}
1940
+ if value:
1941
+ data["value"] = value
1942
+ if limit:
1943
+ data["limit"] = limit
1944
+ if label:
1945
+ data["label"] = label
1946
+ response = requests.patch(
1947
+ f"{self.base_url}/{self.api_prefix}/blocks/{block_id}",
1948
+ headers=self.headers,
1949
+ json=data,
1950
+ )
1951
+ if response.status_code != 200:
1952
+ raise ValueError(f"Failed to update block: {response.text}")
1953
+ return Block(**response.json())
1954
+
1568
1955
 
1569
1956
  class LocalClient(AbstractClient):
1570
1957
  """
@@ -1662,6 +2049,11 @@ class LocalClient(AbstractClient):
1662
2049
  llm_config: LLMConfig = None,
1663
2050
  # memory
1664
2051
  memory: Memory = ChatMemory(human=get_human_text(DEFAULT_HUMAN), persona=get_persona_text(DEFAULT_PERSONA)),
2052
+ # TODO: change to this when we are ready to migrate all the tests/examples (matches the REST API)
2053
+ # memory_blocks=[
2054
+ # {"label": "human", "value": get_human_text(DEFAULT_HUMAN), "limit": 5000},
2055
+ # {"label": "persona", "value": get_persona_text(DEFAULT_PERSONA), "limit": 5000},
2056
+ # ],
1665
2057
  # system
1666
2058
  system: Optional[str] = None,
1667
2059
  # tools
@@ -1680,7 +2072,7 @@ class LocalClient(AbstractClient):
1680
2072
  name (str): Name of the agent
1681
2073
  embedding_config (EmbeddingConfig): Embedding configuration
1682
2074
  llm_config (LLMConfig): LLM configuration
1683
- memory (Memory): Memory configuration
2075
+ memory_blocks (List[Dict]): List of configurations for the memory blocks (placed in core-memory)
1684
2076
  system (str): System configuration
1685
2077
  tools (List[str]): List of tools
1686
2078
  tool_rules (Optional[List[BaseToolRule]]): List of tool rules
@@ -1702,14 +2094,7 @@ class LocalClient(AbstractClient):
1702
2094
  tool_names += tools
1703
2095
  if include_base_tools:
1704
2096
  tool_names += BASE_TOOLS
1705
-
1706
- # add memory tools
1707
- memory_functions = get_memory_functions(memory)
1708
- for func_name, func in memory_functions.items():
1709
- tool = self.create_tool(func, name=func_name, tags=["memory", "letta-base"])
1710
- tool_names.append(tool.name)
1711
-
1712
- self.interface.clear()
2097
+ tool_names += BASE_MEMORY_TOOLS
1713
2098
 
1714
2099
  # check if default configs are provided
1715
2100
  assert embedding_config or self._default_embedding_config, f"Embedding config must be provided"
@@ -1721,7 +2106,10 @@ class LocalClient(AbstractClient):
1721
2106
  name=name,
1722
2107
  description=description,
1723
2108
  metadata_=metadata,
1724
- memory=memory,
2109
+ # memory=memory,
2110
+ memory_blocks=[],
2111
+ # memory_blocks = memory.get_blocks(),
2112
+ # memory_tools=memory_tools,
1725
2113
  tools=tool_names,
1726
2114
  tool_rules=tool_rules,
1727
2115
  system=system,
@@ -1733,7 +2121,18 @@ class LocalClient(AbstractClient):
1733
2121
  ),
1734
2122
  actor=self.user,
1735
2123
  )
1736
- return agent_state
2124
+
2125
+ # TODO: remove when we fully migrate to block creation CreateAgent model
2126
+ # Link additional blocks to the agent (block ids created on the client)
2127
+ # This needs to happen since the create agent does not allow passing in blocks which have already been persisted and have an ID
2128
+ # So we create the agent and then link the blocks afterwards
2129
+ user = self.server.get_user_or_default(self.user_id)
2130
+ for block in memory.get_blocks():
2131
+ self.server.block_manager.create_or_update_block(block, actor=user)
2132
+ self.server.link_block_to_agent_memory(user_id=self.user_id, agent_id=agent_state.id, block_id=block.id)
2133
+
2134
+ # TODO: get full agent state
2135
+ return self.server.get_agent(agent_state.id)
1737
2136
 
1738
2137
  def update_message(
1739
2138
  self,
@@ -1770,7 +2169,6 @@ class LocalClient(AbstractClient):
1770
2169
  llm_config: Optional[LLMConfig] = None,
1771
2170
  embedding_config: Optional[EmbeddingConfig] = None,
1772
2171
  message_ids: Optional[List[str]] = None,
1773
- memory: Optional[Memory] = None,
1774
2172
  ):
1775
2173
  """
1776
2174
  Update an existing agent
@@ -1785,26 +2183,25 @@ class LocalClient(AbstractClient):
1785
2183
  llm_config (LLMConfig): LLM configuration
1786
2184
  embedding_config (EmbeddingConfig): Embedding configuration
1787
2185
  message_ids (List[str]): List of message IDs
1788
- memory (Memory): Memory configuration
1789
2186
  tags (List[str]): Tags for filtering agents
1790
2187
 
1791
2188
  Returns:
1792
2189
  agent_state (AgentState): State of the updated agent
1793
2190
  """
2191
+ # TODO: add the abilitty to reset linked block_ids
1794
2192
  self.interface.clear()
1795
2193
  agent_state = self.server.update_agent(
1796
2194
  UpdateAgentState(
1797
2195
  id=agent_id,
1798
2196
  name=name,
1799
2197
  system=system,
1800
- tools=tools,
2198
+ tool_names=tools,
1801
2199
  tags=tags,
1802
2200
  description=description,
1803
2201
  metadata_=metadata,
1804
2202
  llm_config=llm_config,
1805
2203
  embedding_config=embedding_config,
1806
2204
  message_ids=message_ids,
1807
- memory=memory,
1808
2205
  ),
1809
2206
  actor=self.user,
1810
2207
  )
@@ -1943,7 +2340,7 @@ class LocalClient(AbstractClient):
1943
2340
 
1944
2341
  """
1945
2342
  # TODO: implement this (not sure what it should look like)
1946
- memory = self.server.update_agent_core_memory(user_id=self.user_id, agent_id=agent_id, new_memory_contents={section: value})
2343
+ memory = self.server.update_agent_core_memory(user_id=self.user_id, agent_id=agent_id, label=section, value=value)
1947
2344
  return memory
1948
2345
 
1949
2346
  def get_archival_memory_summary(self, agent_id: str) -> ArchivalMemorySummary:
@@ -1989,7 +2386,6 @@ class LocalClient(AbstractClient):
1989
2386
  self,
1990
2387
  agent_id: str,
1991
2388
  messages: List[Union[Message | MessageCreate]],
1992
- include_full_message: Optional[bool] = False,
1993
2389
  ):
1994
2390
  """
1995
2391
  Send pre-packed messages to an agent.
@@ -2009,15 +2405,7 @@ class LocalClient(AbstractClient):
2009
2405
  self.save()
2010
2406
 
2011
2407
  # format messages
2012
- messages = self.interface.to_list()
2013
- if include_full_message:
2014
- letta_messages = messages
2015
- else:
2016
- letta_messages = []
2017
- for m in messages:
2018
- letta_messages += m.to_letta_message()
2019
-
2020
- return LettaResponse(messages=letta_messages, usage=usage)
2408
+ return LettaResponse(messages=messages, usage=usage)
2021
2409
 
2022
2410
  def send_message(
2023
2411
  self,
@@ -2028,7 +2416,6 @@ class LocalClient(AbstractClient):
2028
2416
  agent_name: Optional[str] = None,
2029
2417
  stream_steps: bool = False,
2030
2418
  stream_tokens: bool = False,
2031
- include_full_message: Optional[bool] = False,
2032
2419
  ) -> LettaResponse:
2033
2420
  """
2034
2421
  Send a message to an agent
@@ -2077,16 +2464,13 @@ class LocalClient(AbstractClient):
2077
2464
 
2078
2465
  # format messages
2079
2466
  messages = self.interface.to_list()
2080
- if include_full_message:
2081
- letta_messages = messages
2082
- else:
2083
- letta_messages = []
2084
- for m in messages:
2085
- letta_messages += m.to_letta_message()
2467
+ letta_messages = []
2468
+ for m in messages:
2469
+ letta_messages += m.to_letta_message()
2086
2470
 
2087
2471
  return LettaResponse(messages=letta_messages, usage=usage)
2088
2472
 
2089
- def user_message(self, agent_id: str, message: str, include_full_message: Optional[bool] = False) -> LettaResponse:
2473
+ def user_message(self, agent_id: str, message: str) -> LettaResponse:
2090
2474
  """
2091
2475
  Send a message to an agent as a user
2092
2476
 
@@ -2098,7 +2482,7 @@ class LocalClient(AbstractClient):
2098
2482
  response (LettaResponse): Response from the agent
2099
2483
  """
2100
2484
  self.interface.clear()
2101
- return self.send_message(role="user", agent_id=agent_id, message=message, include_full_message=include_full_message)
2485
+ return self.send_message(role="user", agent_id=agent_id, message=message)
2102
2486
 
2103
2487
  def run_command(self, agent_id: str, command: str) -> LettaResponse:
2104
2488
  """
@@ -2302,7 +2686,6 @@ class LocalClient(AbstractClient):
2302
2686
  tool_create = ToolCreate.from_composio(action=action)
2303
2687
  return self.server.tool_manager.create_or_update_tool(pydantic_tool=Tool(**tool_create.model_dump()), actor=self.user)
2304
2688
 
2305
- # TODO: Use the above function `add_tool` here as there is duplicate logic
2306
2689
  def create_tool(
2307
2690
  self,
2308
2691
  func,
@@ -2330,6 +2713,42 @@ class LocalClient(AbstractClient):
2330
2713
  if not tags:
2331
2714
  tags = []
2332
2715
 
2716
+ # call server function
2717
+ return self.server.tool_manager.create_tool(
2718
+ Tool(
2719
+ source_type=source_type,
2720
+ source_code=source_code,
2721
+ name=name,
2722
+ tags=tags,
2723
+ description=description,
2724
+ ),
2725
+ actor=self.user,
2726
+ )
2727
+
2728
+ def create_or_update_tool(
2729
+ self,
2730
+ func,
2731
+ name: Optional[str] = None,
2732
+ tags: Optional[List[str]] = None,
2733
+ description: Optional[str] = None,
2734
+ ) -> Tool:
2735
+ """
2736
+ 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.
2737
+
2738
+ Args:
2739
+ func (callable): The function to create a tool for.
2740
+ name: (str): Name of the tool (must be unique per-user.)
2741
+ tags (Optional[List[str]], optional): Tags for the tool. Defaults to None.
2742
+ description (str, optional): The description.
2743
+
2744
+ Returns:
2745
+ tool (Tool): The created tool.
2746
+ """
2747
+ source_code = parse_source_code(func)
2748
+ source_type = "python"
2749
+ if not tags:
2750
+ tags = []
2751
+
2333
2752
  # call server function
2334
2753
  return self.server.tool_manager.create_or_update_tool(
2335
2754
  Tool(
@@ -2655,7 +3074,6 @@ class LocalClient(AbstractClient):
2655
3074
  after=after,
2656
3075
  limit=limit,
2657
3076
  reverse=True,
2658
- return_message_object=True,
2659
3077
  )
2660
3078
 
2661
3079
  def list_blocks(self, label: Optional[str] = None, templates_only: Optional[bool] = True) -> List[Block]:
@@ -2671,7 +3089,9 @@ class LocalClient(AbstractClient):
2671
3089
  """
2672
3090
  return self.server.block_manager.get_blocks(actor=self.user, label=label, is_template=templates_only)
2673
3091
 
2674
- def create_block(self, label: str, value: str, template_name: Optional[str] = None, is_template: bool = False) -> Block: #
3092
+ def create_block(
3093
+ self, label: str, value: str, limit: Optional[int] = None, template_name: Optional[str] = None, is_template: bool = False
3094
+ ) -> Block: #
2675
3095
  """
2676
3096
  Create a block
2677
3097
 
@@ -2679,13 +3099,15 @@ class LocalClient(AbstractClient):
2679
3099
  label (str): Label of the block
2680
3100
  name (str): Name of the block
2681
3101
  text (str): Text of the block
3102
+ limit (int): Character of the block
2682
3103
 
2683
3104
  Returns:
2684
3105
  block (Block): Created block
2685
3106
  """
2686
- return self.server.block_manager.create_or_update_block(
2687
- Block(label=label, template_name=template_name, value=value, is_template=is_template), actor=self.user
2688
- )
3107
+ block = Block(label=label, template_name=template_name, value=value, is_template=is_template)
3108
+ if limit:
3109
+ block.limit = limit
3110
+ return self.server.block_manager.create_or_update_block(block, actor=self.user)
2689
3111
 
2690
3112
  def update_block(self, block_id: str, name: Optional[str] = None, text: Optional[str] = None, limit: Optional[int] = None) -> Block:
2691
3113
  """
@@ -2773,3 +3195,209 @@ class LocalClient(AbstractClient):
2773
3195
 
2774
3196
  def delete_org(self, org_id: str) -> Organization:
2775
3197
  return self.server.organization_manager.delete_organization_by_id(org_id=org_id)
3198
+
3199
+ def create_sandbox_config(self, config: Union[LocalSandboxConfig, E2BSandboxConfig]) -> SandboxConfig:
3200
+ """
3201
+ Create a new sandbox configuration.
3202
+ """
3203
+ config_create = SandboxConfigCreate(config=config)
3204
+ return self.server.sandbox_config_manager.create_or_update_sandbox_config(sandbox_config_create=config_create, actor=self.user)
3205
+
3206
+ def update_sandbox_config(self, sandbox_config_id: str, config: Union[LocalSandboxConfig, E2BSandboxConfig]) -> SandboxConfig:
3207
+ """
3208
+ Update an existing sandbox configuration.
3209
+ """
3210
+ sandbox_update = SandboxConfigUpdate(config=config)
3211
+ return self.server.sandbox_config_manager.update_sandbox_config(
3212
+ sandbox_config_id=sandbox_config_id, sandbox_update=sandbox_update, actor=self.user
3213
+ )
3214
+
3215
+ def delete_sandbox_config(self, sandbox_config_id: str) -> None:
3216
+ """
3217
+ Delete a sandbox configuration.
3218
+ """
3219
+ return self.server.sandbox_config_manager.delete_sandbox_config(sandbox_config_id=sandbox_config_id, actor=self.user)
3220
+
3221
+ def list_sandbox_configs(self, limit: int = 50, cursor: Optional[str] = None) -> List[SandboxConfig]:
3222
+ """
3223
+ List all sandbox configurations.
3224
+ """
3225
+ return self.server.sandbox_config_manager.list_sandbox_configs(actor=self.user, limit=limit, cursor=cursor)
3226
+
3227
+ def create_sandbox_env_var(
3228
+ self, sandbox_config_id: str, key: str, value: str, description: Optional[str] = None
3229
+ ) -> SandboxEnvironmentVariable:
3230
+ """
3231
+ Create a new environment variable for a sandbox configuration.
3232
+ """
3233
+ env_var_create = SandboxEnvironmentVariableCreate(key=key, value=value, description=description)
3234
+ return self.server.sandbox_config_manager.create_sandbox_env_var(
3235
+ env_var_create=env_var_create, sandbox_config_id=sandbox_config_id, actor=self.user
3236
+ )
3237
+
3238
+ def update_sandbox_env_var(
3239
+ self, env_var_id: str, key: Optional[str] = None, value: Optional[str] = None, description: Optional[str] = None
3240
+ ) -> SandboxEnvironmentVariable:
3241
+ """
3242
+ Update an existing environment variable.
3243
+ """
3244
+ env_var_update = SandboxEnvironmentVariableUpdate(key=key, value=value, description=description)
3245
+ return self.server.sandbox_config_manager.update_sandbox_env_var(
3246
+ env_var_id=env_var_id, env_var_update=env_var_update, actor=self.user
3247
+ )
3248
+
3249
+ def delete_sandbox_env_var(self, env_var_id: str) -> None:
3250
+ """
3251
+ Delete an environment variable by its ID.
3252
+ """
3253
+ return self.server.sandbox_config_manager.delete_sandbox_env_var(env_var_id=env_var_id, actor=self.user)
3254
+
3255
+ def list_sandbox_env_vars(
3256
+ self, sandbox_config_id: str, limit: int = 50, cursor: Optional[str] = None
3257
+ ) -> List[SandboxEnvironmentVariable]:
3258
+ """
3259
+ List all environment variables associated with a sandbox configuration.
3260
+ """
3261
+ return self.server.sandbox_config_manager.list_sandbox_env_vars(
3262
+ sandbox_config_id=sandbox_config_id, actor=self.user, limit=limit, cursor=cursor
3263
+ )
3264
+
3265
+ def update_agent_memory_block_label(self, agent_id: str, current_label: str, new_label: str) -> Memory:
3266
+ """Rename a block in the agent's core memory
3267
+
3268
+ Args:
3269
+ agent_id (str): The agent ID
3270
+ current_label (str): The current label of the block
3271
+ new_label (str): The new label of the block
3272
+
3273
+ Returns:
3274
+ memory (Memory): The updated memory
3275
+ """
3276
+ block = self.get_agent_memory_block(agent_id, current_label)
3277
+ return self.update_block(block.id, label=new_label)
3278
+
3279
+ # TODO: remove this
3280
+ def add_agent_memory_block(self, agent_id: str, create_block: CreateBlock) -> Memory:
3281
+ """
3282
+ Create and link a memory block to an agent's core memory
3283
+
3284
+ Args:
3285
+ agent_id (str): The agent ID
3286
+ create_block (CreateBlock): The block to create
3287
+
3288
+ Returns:
3289
+ memory (Memory): The updated memory
3290
+ """
3291
+ block_req = Block(**create_block.model_dump())
3292
+ block = self.server.block_manager.create_or_update_block(actor=self.user, block=block_req)
3293
+ # Link the block to the agent
3294
+ updated_memory = self.server.link_block_to_agent_memory(user_id=self.user_id, agent_id=agent_id, block_id=block.id)
3295
+ return updated_memory
3296
+
3297
+ def link_agent_memory_block(self, agent_id: str, block_id: str) -> Memory:
3298
+ """
3299
+ Link a block to an agent's core memory
3300
+
3301
+ Args:
3302
+ agent_id (str): The agent ID
3303
+ block_id (str): The block ID
3304
+
3305
+ Returns:
3306
+ memory (Memory): The updated memory
3307
+ """
3308
+ return self.server.link_block_to_agent_memory(user_id=self.user_id, agent_id=agent_id, block_id=block_id)
3309
+
3310
+ def remove_agent_memory_block(self, agent_id: str, block_label: str) -> Memory:
3311
+ """
3312
+ Unlike a block from the agent's core memory
3313
+
3314
+ Args:
3315
+ agent_id (str): The agent ID
3316
+ block_label (str): The block label
3317
+
3318
+ Returns:
3319
+ memory (Memory): The updated memory
3320
+ """
3321
+ return self.server.unlink_block_from_agent_memory(user_id=self.user_id, agent_id=agent_id, block_label=block_label)
3322
+
3323
+ def get_agent_memory_blocks(self, agent_id: str) -> List[Block]:
3324
+ """
3325
+ Get all the blocks in the agent's core memory
3326
+
3327
+ Args:
3328
+ agent_id (str): The agent ID
3329
+
3330
+ Returns:
3331
+ blocks (List[Block]): The blocks in the agent's core memory
3332
+ """
3333
+ block_ids = self.server.blocks_agents_manager.list_block_ids_for_agent(agent_id=agent_id)
3334
+ return [self.server.block_manager.get_block_by_id(block_id, actor=self.user) for block_id in block_ids]
3335
+
3336
+ def get_agent_memory_block(self, agent_id: str, label: str) -> Block:
3337
+ """
3338
+ Get a block in the agent's core memory by its label
3339
+
3340
+ Args:
3341
+ agent_id (str): The agent ID
3342
+ label (str): The label in the agent's core memory
3343
+
3344
+ Returns:
3345
+ block (Block): The block corresponding to the label
3346
+ """
3347
+ block_id = self.server.blocks_agents_manager.get_block_id_for_label(agent_id=agent_id, block_label=label)
3348
+ return self.server.block_manager.get_block_by_id(block_id, actor=self.user)
3349
+
3350
+ def update_agent_memory_block(
3351
+ self,
3352
+ agent_id: str,
3353
+ label: str,
3354
+ value: Optional[str] = None,
3355
+ limit: Optional[int] = None,
3356
+ ):
3357
+ """
3358
+ Update a block in the agent's core memory by specifying its label
3359
+
3360
+ Args:
3361
+ agent_id (str): The agent ID
3362
+ label (str): The label of the block
3363
+ value (str): The new value of the block
3364
+ limit (int): The new limit of the block
3365
+
3366
+ Returns:
3367
+ block (Block): The updated block
3368
+ """
3369
+ block = self.get_agent_memory_block(agent_id, label)
3370
+ data = {}
3371
+ if value:
3372
+ data["value"] = value
3373
+ if limit:
3374
+ data["limit"] = limit
3375
+ return self.server.block_manager.update_block(block.id, actor=self.user, block_update=BlockUpdate(**data))
3376
+
3377
+ def update_block(
3378
+ self,
3379
+ block_id: str,
3380
+ label: Optional[str] = None,
3381
+ value: Optional[str] = None,
3382
+ limit: Optional[int] = None,
3383
+ ):
3384
+ """
3385
+ Update a block given the ID with the provided fields
3386
+
3387
+ Args:
3388
+ block_id (str): ID of the block
3389
+ label (str): Label to assign to the block
3390
+ value (str): Value to assign to the block
3391
+ limit (int): Token limit to assign to the block
3392
+
3393
+ Returns:
3394
+ block (Block): Updated block
3395
+ """
3396
+ data = {}
3397
+ if value:
3398
+ data["value"] = value
3399
+ if limit:
3400
+ data["limit"] = limit
3401
+ if label:
3402
+ data["label"] = label
3403
+ return self.server.block_manager.update_block(block_id, actor=self.user, block_update=BlockUpdate(**data))