letta-nightly 0.13.0.dev20251031104146__py3-none-any.whl → 0.13.1.dev20251101010313__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 (105) hide show
  1. letta/__init__.py +1 -1
  2. letta/adapters/simple_llm_stream_adapter.py +1 -0
  3. letta/agents/letta_agent_v2.py +8 -0
  4. letta/agents/letta_agent_v3.py +127 -27
  5. letta/agents/temporal/activities/__init__.py +25 -0
  6. letta/agents/temporal/activities/create_messages.py +26 -0
  7. letta/agents/temporal/activities/create_step.py +57 -0
  8. letta/agents/temporal/activities/example_activity.py +9 -0
  9. letta/agents/temporal/activities/execute_tool.py +130 -0
  10. letta/agents/temporal/activities/llm_request.py +114 -0
  11. letta/agents/temporal/activities/prepare_messages.py +27 -0
  12. letta/agents/temporal/activities/refresh_context.py +160 -0
  13. letta/agents/temporal/activities/summarize_conversation_history.py +77 -0
  14. letta/agents/temporal/activities/update_message_ids.py +25 -0
  15. letta/agents/temporal/activities/update_run.py +43 -0
  16. letta/agents/temporal/constants.py +59 -0
  17. letta/agents/temporal/temporal_agent_workflow.py +704 -0
  18. letta/agents/temporal/types.py +275 -0
  19. letta/constants.py +11 -0
  20. letta/errors.py +4 -0
  21. letta/functions/function_sets/base.py +0 -11
  22. letta/groups/helpers.py +7 -1
  23. letta/groups/sleeptime_multi_agent_v4.py +4 -3
  24. letta/interfaces/anthropic_streaming_interface.py +0 -1
  25. letta/interfaces/openai_streaming_interface.py +103 -100
  26. letta/llm_api/anthropic_client.py +57 -12
  27. letta/llm_api/bedrock_client.py +1 -0
  28. letta/llm_api/deepseek_client.py +3 -2
  29. letta/llm_api/google_vertex_client.py +5 -4
  30. letta/llm_api/groq_client.py +1 -0
  31. letta/llm_api/llm_client_base.py +15 -1
  32. letta/llm_api/openai.py +2 -2
  33. letta/llm_api/openai_client.py +17 -3
  34. letta/llm_api/xai_client.py +1 -0
  35. letta/orm/agent.py +3 -0
  36. letta/orm/organization.py +4 -0
  37. letta/orm/sqlalchemy_base.py +7 -0
  38. letta/otel/tracing.py +131 -4
  39. letta/schemas/agent.py +108 -40
  40. letta/schemas/agent_file.py +10 -10
  41. letta/schemas/block.py +22 -3
  42. letta/schemas/enums.py +21 -0
  43. letta/schemas/environment_variables.py +3 -2
  44. letta/schemas/group.py +3 -3
  45. letta/schemas/letta_response.py +36 -4
  46. letta/schemas/llm_batch_job.py +3 -3
  47. letta/schemas/llm_config.py +123 -4
  48. letta/schemas/mcp.py +3 -2
  49. letta/schemas/mcp_server.py +3 -2
  50. letta/schemas/message.py +167 -49
  51. letta/schemas/model.py +265 -0
  52. letta/schemas/organization.py +2 -1
  53. letta/schemas/passage.py +2 -1
  54. letta/schemas/provider_trace.py +2 -1
  55. letta/schemas/providers/openrouter.py +1 -2
  56. letta/schemas/run_metrics.py +2 -1
  57. letta/schemas/sandbox_config.py +3 -1
  58. letta/schemas/step_metrics.py +2 -1
  59. letta/schemas/tool_rule.py +2 -2
  60. letta/schemas/user.py +2 -1
  61. letta/server/rest_api/app.py +5 -1
  62. letta/server/rest_api/routers/v1/__init__.py +4 -0
  63. letta/server/rest_api/routers/v1/agents.py +71 -9
  64. letta/server/rest_api/routers/v1/blocks.py +7 -7
  65. letta/server/rest_api/routers/v1/groups.py +40 -0
  66. letta/server/rest_api/routers/v1/identities.py +2 -2
  67. letta/server/rest_api/routers/v1/internal_agents.py +31 -0
  68. letta/server/rest_api/routers/v1/internal_blocks.py +177 -0
  69. letta/server/rest_api/routers/v1/internal_runs.py +25 -1
  70. letta/server/rest_api/routers/v1/runs.py +2 -22
  71. letta/server/rest_api/routers/v1/tools.py +12 -1
  72. letta/server/server.py +20 -4
  73. letta/services/agent_manager.py +4 -4
  74. letta/services/archive_manager.py +16 -0
  75. letta/services/group_manager.py +44 -0
  76. letta/services/helpers/run_manager_helper.py +2 -2
  77. letta/services/lettuce/lettuce_client.py +148 -0
  78. letta/services/mcp/base_client.py +9 -3
  79. letta/services/run_manager.py +148 -37
  80. letta/services/source_manager.py +91 -3
  81. letta/services/step_manager.py +2 -3
  82. letta/services/streaming_service.py +52 -13
  83. letta/services/summarizer/summarizer.py +28 -2
  84. letta/services/tool_executor/builtin_tool_executor.py +1 -1
  85. letta/services/tool_executor/core_tool_executor.py +2 -117
  86. letta/services/tool_sandbox/e2b_sandbox.py +4 -1
  87. letta/services/tool_schema_generator.py +2 -2
  88. letta/validators.py +21 -0
  89. {letta_nightly-0.13.0.dev20251031104146.dist-info → letta_nightly-0.13.1.dev20251101010313.dist-info}/METADATA +1 -1
  90. {letta_nightly-0.13.0.dev20251031104146.dist-info → letta_nightly-0.13.1.dev20251101010313.dist-info}/RECORD +93 -87
  91. letta/agent.py +0 -1758
  92. letta/cli/cli_load.py +0 -16
  93. letta/client/__init__.py +0 -0
  94. letta/client/streaming.py +0 -95
  95. letta/client/utils.py +0 -78
  96. letta/functions/async_composio_toolset.py +0 -109
  97. letta/functions/composio_helpers.py +0 -96
  98. letta/helpers/composio_helpers.py +0 -38
  99. letta/orm/job_messages.py +0 -33
  100. letta/schemas/providers.py +0 -1617
  101. letta/server/rest_api/routers/openai/chat_completions/chat_completions.py +0 -132
  102. letta/services/tool_executor/composio_tool_executor.py +0 -57
  103. {letta_nightly-0.13.0.dev20251031104146.dist-info → letta_nightly-0.13.1.dev20251101010313.dist-info}/WHEEL +0 -0
  104. {letta_nightly-0.13.0.dev20251031104146.dist-info → letta_nightly-0.13.1.dev20251101010313.dist-info}/entry_points.txt +0 -0
  105. {letta_nightly-0.13.0.dev20251031104146.dist-info → letta_nightly-0.13.1.dev20251101010313.dist-info}/licenses/LICENSE +0 -0
letta/cli/cli_load.py DELETED
@@ -1,16 +0,0 @@
1
- """
2
- This file contains functions for loading data into Letta's archival storage.
3
-
4
- Data can be loaded with the following command, once a load function is defined:
5
- ```
6
- letta load <data-connector-type> --name <dataset-name> [ADDITIONAL ARGS]
7
- ```
8
-
9
- """
10
-
11
- import typer
12
-
13
- app = typer.Typer()
14
-
15
-
16
- default_extensions = "txt,md,pdf"
letta/client/__init__.py DELETED
File without changes
letta/client/streaming.py DELETED
@@ -1,95 +0,0 @@
1
- import json
2
- from typing import Generator, Union, get_args
3
-
4
- import httpx
5
- from httpx_sse import SSEError, connect_sse
6
- from openai.types.chat.chat_completion_chunk import ChatCompletionChunk
7
-
8
- from letta.constants import OPENAI_CONTEXT_WINDOW_ERROR_SUBSTRING
9
- from letta.errors import LLMError
10
- from letta.log import get_logger
11
- from letta.schemas.enums import MessageStreamStatus
12
- from letta.schemas.letta_message import AssistantMessage, HiddenReasoningMessage, ReasoningMessage, ToolCallMessage, ToolReturnMessage
13
- from letta.schemas.letta_response import LettaStreamingResponse
14
- from letta.schemas.usage import LettaUsageStatistics
15
-
16
- logger = get_logger(__name__)
17
-
18
-
19
- def _sse_post(url: str, data: dict, headers: dict) -> Generator[Union[LettaStreamingResponse, ChatCompletionChunk], None, None]:
20
- """
21
- Sends an SSE POST request and yields parsed response chunks.
22
- """
23
- # TODO: Please note his is a very generous timeout for e2b reasons
24
- with httpx.Client(timeout=httpx.Timeout(5 * 60.0, read=5 * 60.0)) as client:
25
- with connect_sse(client, method="POST", url=url, json=data, headers=headers) as event_source:
26
- # Check for immediate HTTP errors before processing the SSE stream
27
- if not event_source.response.is_success:
28
- response_bytes = event_source.response.read()
29
- logger.warning(f"SSE request error: {vars(event_source.response)}")
30
- logger.warning(response_bytes.decode("utf-8"))
31
-
32
- try:
33
- response_dict = json.loads(response_bytes.decode("utf-8"))
34
- error_message = response_dict.get("error", {}).get("message", "")
35
-
36
- if OPENAI_CONTEXT_WINDOW_ERROR_SUBSTRING in error_message:
37
- logger.error(error_message)
38
- raise LLMError(error_message)
39
- except LLMError:
40
- raise
41
- except Exception:
42
- logger.error("Failed to parse SSE message, raising HTTP error")
43
- event_source.response.raise_for_status()
44
-
45
- try:
46
- for sse in event_source.iter_sse():
47
- if sse.data in {status.value for status in MessageStreamStatus}:
48
- yield MessageStreamStatus(sse.data)
49
- if sse.data == MessageStreamStatus.done.value:
50
- # We received the [DONE], so stop reading the stream.
51
- break
52
- else:
53
- chunk_data = json.loads(sse.data)
54
-
55
- if "reasoning" in chunk_data:
56
- yield ReasoningMessage(**chunk_data)
57
- elif chunk_data.get("message_type") == "assistant_message":
58
- yield AssistantMessage(**chunk_data)
59
- elif "hidden_reasoning" in chunk_data:
60
- yield HiddenReasoningMessage(**chunk_data)
61
- elif "tool_call" in chunk_data:
62
- yield ToolCallMessage(**chunk_data)
63
- elif "tool_return" in chunk_data:
64
- yield ToolReturnMessage(**chunk_data)
65
- elif "step_count" in chunk_data:
66
- yield LettaUsageStatistics(**chunk_data)
67
- elif chunk_data.get("object") == get_args(ChatCompletionChunk.__annotations__["object"])[0]:
68
- yield ChatCompletionChunk(**chunk_data)
69
- else:
70
- raise ValueError(f"Unknown message type in chunk_data: {chunk_data}")
71
-
72
- except SSEError as e:
73
- logger.error(f"SSE stream error: {e}")
74
-
75
- if "application/json" in str(e):
76
- response = client.post(url=url, json=data, headers=headers)
77
-
78
- if response.headers.get("Content-Type", "").startswith("application/json"):
79
- error_details = response.json()
80
- logger.error(f"POST Error: {error_details}")
81
- else:
82
- logger.error("Failed to retrieve JSON error message via retry.")
83
-
84
- raise e
85
-
86
- except Exception as e:
87
- logger.error(f"Unexpected exception: {e}")
88
-
89
- if event_source.response.request:
90
- logger.error(f"HTTP Request: {vars(event_source.response.request)}")
91
- if event_source.response:
92
- logger.error(f"HTTP Status: {event_source.response.status_code}")
93
- logger.error(f"HTTP Headers: {event_source.response.headers}")
94
-
95
- raise e
letta/client/utils.py DELETED
@@ -1,78 +0,0 @@
1
- import re
2
- from datetime import datetime
3
- from typing import Optional
4
-
5
- from IPython.display import HTML, display
6
- from sqlalchemy.testing.plugin.plugin_base import warnings
7
-
8
- from letta.local_llm.constants import ASSISTANT_MESSAGE_CLI_SYMBOL, INNER_THOUGHTS_CLI_SYMBOL
9
-
10
-
11
- def pprint(messages):
12
- """Utility function for pretty-printing the output of client.send_message in notebooks"""
13
-
14
- css_styles = """
15
- <style>
16
- .terminal {
17
- background-color: #002b36;
18
- color: #839496;
19
- font-family: 'Courier New', Courier, monospace;
20
- padding: 10px;
21
- border-radius: 5px;
22
- }
23
- .terminal strong {
24
- color: #b58900;
25
- }
26
- .terminal .function-return {
27
- color: #2aa198;
28
- }
29
- .terminal .internal-monologue {
30
- color: #d33682;
31
- }
32
- .terminal .function-call {
33
- color: #2aa198;
34
- }
35
- .terminal .assistant-message {
36
- color: #859900;
37
- }
38
- .terminal pre {
39
- color: #839496;
40
- }
41
- </style>
42
- """
43
-
44
- html_content = css_styles + "<div class='terminal'>"
45
- for message in messages:
46
- date_str = message["date"]
47
- date_formatted = datetime.fromisoformat(date_str.replace("Z", "+00:00")).strftime("%Y-%m-%d %H:%M:%S")
48
-
49
- if "function_return" in message:
50
- return_string = message["function_return"]
51
- return_status = message["status"]
52
- html_content += f"<p><strong>🛠️ [{date_formatted}] Function Return ({return_status}):</strong></p>"
53
- html_content += f"<p class='function-return'>{return_string}</p>"
54
- elif "internal_monologue" in message:
55
- html_content += f"<p><strong>{INNER_THOUGHTS_CLI_SYMBOL} [{date_formatted}] Internal Monologue:</strong></p>"
56
- html_content += f"<p class='internal-monologue'>{message['internal_monologue']}</p>"
57
- elif "function_call" in message:
58
- html_content += f"<p><strong>🛠️ [[{date_formatted}] Function Call:</strong></p>"
59
- html_content += f"<p class='function-call'>{message['function_call']}</p>"
60
- elif "assistant_message" in message:
61
- html_content += f"<p><strong>{ASSISTANT_MESSAGE_CLI_SYMBOL} [{date_formatted}] Assistant Message:</strong></p>"
62
- html_content += f"<p class='assistant-message'>{message['assistant_message']}</p>"
63
- html_content += "<br>"
64
- html_content += "</div>"
65
-
66
- display(HTML(html_content))
67
-
68
-
69
- def derive_function_name_regex(function_string: str) -> Optional[str]:
70
- # Regular expression to match the function name
71
- match = re.search(r"def\s+([a-zA-Z_]\w*)\s*\(", function_string)
72
-
73
- if match:
74
- function_name = match.group(1)
75
- return function_name
76
- else:
77
- warnings.warn("No function name found.")
78
- return None
@@ -1,109 +0,0 @@
1
- import json
2
- from typing import Any
3
-
4
- import aiohttp
5
- from composio import ComposioToolSet as BaseComposioToolSet
6
- from composio.exceptions import (
7
- ApiKeyNotProvidedError,
8
- ComposioSDKError,
9
- ConnectedAccountNotFoundError,
10
- EnumMetadataNotFound,
11
- EnumStringNotFound,
12
- )
13
-
14
-
15
- class AsyncComposioToolSet(BaseComposioToolSet, runtime="letta", description_char_limit=1024):
16
- """
17
- Async version of ComposioToolSet client for interacting with Composio API
18
- Used to asynchronously hit the execute action endpoint
19
-
20
- https://docs.composio.dev/api-reference/api-reference/v3/tools/post-api-v-3-tools-execute-action
21
- """
22
-
23
- def __init__(self, api_key: str, entity_id: str, lock: bool = True):
24
- """
25
- Initialize the AsyncComposioToolSet client
26
-
27
- Args:
28
- api_key (str): Your Composio API key
29
- entity_id (str): Your Composio entity ID
30
- lock (bool): Whether to use locking (default: True)
31
- """
32
- super().__init__(api_key=api_key, entity_id=entity_id, lock=lock)
33
-
34
- self.headers = {
35
- "Content-Type": "application/json",
36
- "X-API-Key": self._api_key,
37
- }
38
-
39
- async def execute_action(
40
- self,
41
- action: str,
42
- params: dict[str, Any] = {},
43
- ) -> dict[str, Any]:
44
- """
45
- Execute an action asynchronously using the Composio API
46
-
47
- Args:
48
- action (str): The name of the action to execute
49
- params (dict[str, Any], optional): Parameters for the action
50
-
51
- Returns:
52
- dict[str, Any]: The API response
53
-
54
- Raises:
55
- ApiKeyNotProvidedError: if the API key is not provided
56
- ComposioSDKError: if a general Composio SDK error occurs
57
- ConnectedAccountNotFoundError: if the connected account is not found
58
- EnumMetadataNotFound: if enum metadata is not found
59
- EnumStringNotFound: if enum string is not found
60
- aiohttp.ClientError: if a network-related error occurs
61
- ValueError: if an error with the parameters or response occurs
62
- """
63
- API_VERSION = "v3"
64
- endpoint = f"{self._base_url}/{API_VERSION}/tools/execute/{action}"
65
-
66
- json_payload = {
67
- "entity_id": self.entity_id,
68
- "arguments": params or {},
69
- }
70
-
71
- try:
72
- async with aiohttp.ClientSession() as session:
73
- async with session.post(endpoint, headers=self.headers, json=json_payload) as response:
74
- print(response, response.status, response.reason, response.content)
75
- if response.status == 200:
76
- return await response.json()
77
- else:
78
- error_text = await response.text()
79
- try:
80
- error_json = json.loads(error_text)
81
- error_message = error_json.get("message", error_text)
82
- error_code = error_json.get("code")
83
-
84
- # Handle specific error codes from Composio API
85
- if error_code == 10401 or "API_KEY_NOT_FOUND" in error_message:
86
- raise ApiKeyNotProvidedError()
87
- if (
88
- "connected account not found" in error_message.lower()
89
- or "no connected account found" in error_message.lower()
90
- ):
91
- raise ConnectedAccountNotFoundError(f"Connected account not found: {error_message}")
92
- if "enum metadata not found" in error_message.lower():
93
- raise EnumMetadataNotFound(f"Enum metadata not found: {error_message}")
94
- if "enum string not found" in error_message.lower():
95
- raise EnumStringNotFound(f"Enum string not found: {error_message}")
96
- except json.JSONDecodeError:
97
- error_message = error_text
98
-
99
- # If no specific error was identified, raise a general error
100
- raise ValueError(f"API request failed with status {response.status}: {error_message}")
101
- except aiohttp.ClientError as e:
102
- # Wrap network errors in ComposioSDKError
103
- raise ComposioSDKError(f"Network error when calling Composio API: {str(e)}")
104
- except ValueError:
105
- # Re-raise ValueError (which could be our custom error message or a JSON parsing error)
106
- raise
107
- except Exception as e:
108
- # Catch any other exceptions and wrap them in ComposioSDKError
109
- raise ComposioSDKError(f"Unexpected error when calling Composio API: {str(e)}")
@@ -1,96 +0,0 @@
1
- import os
2
- from typing import Any, Optional
3
-
4
- from composio.constants import DEFAULT_ENTITY_ID
5
- from composio.exceptions import (
6
- ApiKeyNotProvidedError,
7
- ComposioSDKError,
8
- ConnectedAccountNotFoundError,
9
- EnumMetadataNotFound,
10
- EnumStringNotFound,
11
- )
12
-
13
- from letta.constants import COMPOSIO_ENTITY_ENV_VAR_KEY
14
- from letta.functions.async_composio_toolset import AsyncComposioToolSet
15
- from letta.utils import run_async_task
16
-
17
-
18
- # TODO: This is kind of hacky, as this is used to search up the action later on composio's side
19
- # TODO: So be very careful changing/removing these pair of functions
20
- def _generate_func_name_from_composio_action(action_name: str) -> str:
21
- """
22
- Generates the composio function name from the composio action.
23
-
24
- Args:
25
- action_name: The composio action name
26
-
27
- Returns:
28
- function name
29
- """
30
- return action_name.lower()
31
-
32
-
33
- def generate_composio_action_from_func_name(func_name: str) -> str:
34
- """
35
- Generates the composio action from the composio function name.
36
-
37
- Args:
38
- func_name: The composio function name
39
-
40
- Returns:
41
- composio action name
42
- """
43
- return func_name.upper()
44
-
45
-
46
- def generate_composio_tool_wrapper(action_name: str) -> tuple[str, str]:
47
- # Generate func name
48
- func_name = _generate_func_name_from_composio_action(action_name)
49
-
50
- wrapper_function_str = f"""\
51
- def {func_name}(**kwargs):
52
- raise RuntimeError("Something went wrong - we should never be using the persisted source code for Composio. Please reach out to Letta team")
53
- """
54
-
55
- # Compile safety check
56
- _assert_code_gen_compilable(wrapper_function_str.strip())
57
-
58
- return func_name, wrapper_function_str.strip()
59
-
60
-
61
- async def execute_composio_action_async(
62
- action_name: str, args: dict, api_key: Optional[str] = None, entity_id: Optional[str] = None
63
- ) -> tuple[str, str]:
64
- entity_id = entity_id or os.getenv(COMPOSIO_ENTITY_ENV_VAR_KEY, DEFAULT_ENTITY_ID)
65
- composio_toolset = AsyncComposioToolSet(api_key=api_key, entity_id=entity_id, lock=False)
66
- try:
67
- response = await composio_toolset.execute_action(action=action_name, params=args)
68
- except ApiKeyNotProvidedError as e:
69
- raise RuntimeError(f"API key not provided or invalid for Composio action '{action_name}': {str(e)}")
70
- except ConnectedAccountNotFoundError as e:
71
- raise RuntimeError(f"Connected account not found for Composio action '{action_name}': {str(e)}")
72
- except EnumMetadataNotFound as e:
73
- raise RuntimeError(f"Enum metadata not found for Composio action '{action_name}': {str(e)}")
74
- except EnumStringNotFound as e:
75
- raise RuntimeError(f"Enum string not found for Composio action '{action_name}': {str(e)}")
76
- except ComposioSDKError as e:
77
- raise RuntimeError(f"Composio SDK error while executing action '{action_name}': {str(e)}")
78
- except Exception as e:
79
- print(type(e))
80
- raise RuntimeError(f"An unexpected error occurred in Composio SDK while executing action '{action_name}': {str(e)}")
81
-
82
- if "error" in response and response["error"]:
83
- raise RuntimeError(f"Error while executing action '{action_name}': {str(response['error'])}")
84
-
85
- return response.get("data")
86
-
87
-
88
- def execute_composio_action(action_name: str, args: dict, api_key: Optional[str] = None, entity_id: Optional[str] = None) -> Any:
89
- return run_async_task(execute_composio_action_async(action_name, args, api_key, entity_id))
90
-
91
-
92
- def _assert_code_gen_compilable(code_str):
93
- try:
94
- compile(code_str, "<string>", "exec")
95
- except SyntaxError as e:
96
- print(f"Syntax error in code: {e}")
@@ -1,38 +0,0 @@
1
- from logging import Logger
2
- from typing import Optional
3
-
4
- from letta.schemas.user import User
5
- from letta.services.sandbox_config_manager import SandboxConfigManager
6
- from letta.settings import tool_settings
7
-
8
-
9
- def get_composio_api_key(actor: User, logger: Optional[Logger] = None) -> Optional[str]:
10
- api_keys = SandboxConfigManager().list_sandbox_env_vars_by_key(key="COMPOSIO_API_KEY", actor=actor)
11
- if not api_keys:
12
- if logger:
13
- logger.debug("No API keys found for Composio. Defaulting to the environment variable...")
14
- if tool_settings.composio_api_key:
15
- return tool_settings.composio_api_key
16
- else:
17
- return None
18
- else:
19
- # TODO: Add more protections around this
20
- # Ideally, not tied to a specific sandbox, but for now we just get the first one
21
- # Theoretically possible for someone to have different composio api keys per sandbox
22
- return api_keys[0].value
23
-
24
-
25
- async def get_composio_api_key_async(actor: User, logger: Optional[Logger] = None) -> Optional[str]:
26
- api_keys = await SandboxConfigManager().list_sandbox_env_vars_by_key_async(key="COMPOSIO_API_KEY", actor=actor)
27
- if not api_keys:
28
- if logger:
29
- logger.debug("No API keys found for Composio. Defaulting to the environment variable...")
30
- if tool_settings.composio_api_key:
31
- return tool_settings.composio_api_key
32
- else:
33
- return None
34
- else:
35
- # TODO: Add more protections around this
36
- # Ideally, not tied to a specific sandbox, but for now we just get the first one
37
- # Theoretically possible for someone to have different composio api keys per sandbox
38
- return api_keys[0].value
letta/orm/job_messages.py DELETED
@@ -1,33 +0,0 @@
1
- from typing import TYPE_CHECKING
2
-
3
- from sqlalchemy import ForeignKey, UniqueConstraint
4
- from sqlalchemy.orm import Mapped, mapped_column, relationship
5
-
6
- from letta.orm.sqlalchemy_base import SqlalchemyBase
7
-
8
- if TYPE_CHECKING:
9
- from letta.orm.job import Job
10
- from letta.orm.message import Message
11
-
12
-
13
- class JobMessage(SqlalchemyBase):
14
- """Tracks messages that were created during job execution."""
15
-
16
- __tablename__ = "job_messages"
17
- __table_args__ = (UniqueConstraint("job_id", "message_id", name="unique_job_message"),)
18
-
19
- id: Mapped[int] = mapped_column(primary_key=True, doc="Unique identifier for the job message")
20
- job_id: Mapped[str] = mapped_column(
21
- ForeignKey("jobs.id", ondelete="CASCADE"),
22
- nullable=False, # A job message must belong to a job
23
- doc="ID of the job that created the message",
24
- )
25
- message_id: Mapped[str] = mapped_column(
26
- ForeignKey("messages.id", ondelete="CASCADE"),
27
- nullable=False, # A job message must have a message
28
- doc="ID of the message created by the job",
29
- )
30
-
31
- # Relationships
32
- job: Mapped["Job"] = relationship("Job", back_populates="job_messages")
33
- message: Mapped["Message"] = relationship("Message", back_populates="job_message")