letta-nightly 0.11.7.dev20250911104039__py3-none-any.whl → 0.11.7.dev20250912104045__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.
@@ -213,8 +213,17 @@ class LettaAgentV2(BaseAgentV2):
213
213
 
214
214
  if self.stop_reason is None:
215
215
  self.stop_reason = LettaStopReason(stop_reason=StopReasonType.end_turn.value)
216
- self._request_checkpoint_finish(request_span=request_span, request_start_timestamp_ns=request_start_timestamp_ns)
217
- return LettaResponse(messages=response_letta_messages, stop_reason=self.stop_reason, usage=self.usage)
216
+
217
+ result = LettaResponse(messages=response_letta_messages, stop_reason=self.stop_reason, usage=self.usage)
218
+ if run_id:
219
+ if self.job_update_metadata is None:
220
+ self.job_update_metadata = {}
221
+ self.job_update_metadata["result"] = result.model_dump(mode="json")
222
+
223
+ await self._request_checkpoint_finish(
224
+ request_span=request_span, request_start_timestamp_ns=request_start_timestamp_ns, run_id=run_id
225
+ )
226
+ return result
218
227
 
219
228
  @trace_method
220
229
  async def stream(
@@ -301,7 +310,20 @@ class LettaAgentV2(BaseAgentV2):
301
310
  yield f"data: {self.stop_reason.model_dump_json()}\n\n"
302
311
  raise
303
312
 
304
- self._request_checkpoint_finish(request_span=request_span, request_start_timestamp_ns=request_start_timestamp_ns)
313
+ if run_id:
314
+ letta_messages = Message.to_letta_messages_from_list(
315
+ self.response_messages,
316
+ use_assistant_message=use_assistant_message,
317
+ reverse=False,
318
+ )
319
+ result = LettaResponse(messages=letta_messages, stop_reason=self.stop_reason, usage=self.usage)
320
+ if self.job_update_metadata is None:
321
+ self.job_update_metadata = {}
322
+ self.job_update_metadata["result"] = result.model_dump(mode="json")
323
+
324
+ await self._request_checkpoint_finish(
325
+ request_span=request_span, request_start_timestamp_ns=request_start_timestamp_ns, run_id=run_id
326
+ )
305
327
  for finish_chunk in self.get_finish_chunks_for_stream(self.usage, self.stop_reason):
306
328
  yield f"data: {finish_chunk}\n\n"
307
329
 
@@ -736,11 +758,10 @@ class LettaAgentV2(BaseAgentV2):
736
758
  return None
737
759
 
738
760
  @trace_method
739
- def _request_checkpoint_finish(self, request_span: Span | None, request_start_timestamp_ns: int | None) -> None:
740
- if request_span is not None:
741
- duration_ns = get_utc_timestamp_ns() - request_start_timestamp_ns
742
- request_span.add_event(name="letta_request_ms", attributes={"duration_ms": ns_to_ms(duration_ns)})
743
- request_span.end()
761
+ async def _request_checkpoint_finish(
762
+ self, request_span: Span | None, request_start_timestamp_ns: int | None, run_id: str | None
763
+ ) -> None:
764
+ await self._log_request(request_start_timestamp_ns, request_span, self.job_update_metadata, is_error=False, run_id=run_id)
744
765
  return None
745
766
 
746
767
  @trace_method
@@ -62,11 +62,18 @@ class TurbopufferClient:
62
62
  """
63
63
  from letta.llm_api.llm_client import LLMClient
64
64
 
65
+ # filter out empty strings after stripping
66
+ filtered_texts = [text for text in texts if text.strip()]
67
+
68
+ # skip embedding if no valid texts
69
+ if not filtered_texts:
70
+ return []
71
+
65
72
  embedding_client = LLMClient.create(
66
73
  provider_type=self.default_embedding_config.embedding_endpoint_type,
67
74
  actor=actor,
68
75
  )
69
- embeddings = await embedding_client.request_embeddings(texts, self.default_embedding_config)
76
+ embeddings = await embedding_client.request_embeddings(filtered_texts, self.default_embedding_config)
70
77
  return embeddings
71
78
 
72
79
  @trace_method
@@ -119,8 +126,16 @@ class TurbopufferClient:
119
126
  """
120
127
  from turbopuffer import AsyncTurbopuffer
121
128
 
129
+ # filter out empty text chunks
130
+ filtered_chunks = [(i, text) for i, text in enumerate(text_chunks) if text.strip()]
131
+
132
+ if not filtered_chunks:
133
+ logger.warning("All text chunks were empty, skipping insertion")
134
+ return []
135
+
122
136
  # generate embeddings using the default config
123
- embeddings = await self._generate_embeddings(text_chunks, actor)
137
+ filtered_texts = [text for _, text in filtered_chunks]
138
+ embeddings = await self._generate_embeddings(filtered_texts, actor)
124
139
 
125
140
  namespace_name = await self._get_archive_namespace_name(archive_id)
126
141
 
@@ -152,8 +167,8 @@ class TurbopufferClient:
152
167
  tags_arrays = [] # Store tags as arrays
153
168
  passages = []
154
169
 
155
- for idx, (text, embedding) in enumerate(zip(text_chunks, embeddings)):
156
- passage_id = passage_ids[idx]
170
+ for (original_idx, text), embedding in zip(filtered_chunks, embeddings):
171
+ passage_id = passage_ids[original_idx]
157
172
 
158
173
  # append to columns
159
174
  ids.append(passage_id)
@@ -240,8 +255,16 @@ class TurbopufferClient:
240
255
  """
241
256
  from turbopuffer import AsyncTurbopuffer
242
257
 
258
+ # filter out empty message texts
259
+ filtered_messages = [(i, text) for i, text in enumerate(message_texts) if text.strip()]
260
+
261
+ if not filtered_messages:
262
+ logger.warning("All message texts were empty, skipping insertion")
263
+ return True
264
+
243
265
  # generate embeddings using the default config
244
- embeddings = await self._generate_embeddings(message_texts, actor)
266
+ filtered_texts = [text for _, text in filtered_messages]
267
+ embeddings = await self._generate_embeddings(filtered_texts, actor)
245
268
 
246
269
  namespace_name = await self._get_message_namespace_name(organization_id)
247
270
 
@@ -266,8 +289,10 @@ class TurbopufferClient:
266
289
  project_ids = []
267
290
  template_ids = []
268
291
 
269
- for idx, (text, embedding, role, created_at) in enumerate(zip(message_texts, embeddings, roles, created_ats)):
270
- message_id = message_ids[idx]
292
+ for (original_idx, text), embedding in zip(filtered_messages, embeddings):
293
+ message_id = message_ids[original_idx]
294
+ role = roles[original_idx]
295
+ created_at = created_ats[original_idx]
271
296
 
272
297
  # ensure the provided timestamp is timezone-aware and in UTC
273
298
  if created_at.tzinfo is None:
@@ -1162,8 +1187,15 @@ class TurbopufferClient:
1162
1187
  if not text_chunks:
1163
1188
  return []
1164
1189
 
1190
+ # filter out empty text chunks
1191
+ filtered_chunks = [text for text in text_chunks if text.strip()]
1192
+
1193
+ if not filtered_chunks:
1194
+ logger.warning("All text chunks were empty, skipping file passage insertion")
1195
+ return []
1196
+
1165
1197
  # generate embeddings using the default config
1166
- embeddings = await self._generate_embeddings(text_chunks, actor)
1198
+ embeddings = await self._generate_embeddings(filtered_chunks, actor)
1167
1199
 
1168
1200
  namespace_name = await self._get_file_passages_namespace_name(organization_id)
1169
1201
 
@@ -1189,7 +1221,7 @@ class TurbopufferClient:
1189
1221
  created_ats = []
1190
1222
  passages = []
1191
1223
 
1192
- for idx, (text, embedding) in enumerate(zip(text_chunks, embeddings)):
1224
+ for text, embedding in zip(filtered_chunks, embeddings):
1193
1225
  passage = PydanticPassage(
1194
1226
  text=text,
1195
1227
  file_id=file_id,
@@ -24,7 +24,7 @@ from letta.schemas.letta_stop_reason import LettaStopReason, StopReasonType
24
24
  from letta.schemas.message import Message
25
25
  from letta.schemas.openai.chat_completion_response import FunctionCall, ToolCall
26
26
  from letta.server.rest_api.json_parser import OptimisticJSONParser
27
- from letta.streaming_utils import JSONInnerThoughtsExtractor
27
+ from letta.streaming_utils import FunctionArgumentsStreamHandler, JSONInnerThoughtsExtractor
28
28
  from letta.utils import count_tokens
29
29
 
30
30
  logger = get_logger(__name__)
@@ -53,6 +53,8 @@ class OpenAIStreamingInterface:
53
53
 
54
54
  self.optimistic_json_parser: OptimisticJSONParser = OptimisticJSONParser()
55
55
  self.function_args_reader = JSONInnerThoughtsExtractor(wait_for_first_key=put_inner_thoughts_in_kwarg)
56
+ # Reader that extracts only the assistant message value from send_message args
57
+ self.assistant_message_json_reader = FunctionArgumentsStreamHandler(json_key=self.assistant_message_tool_kwarg)
56
58
  self.function_name_buffer = None
57
59
  self.function_args_buffer = None
58
60
  self.function_id_buffer = None
@@ -274,6 +276,8 @@ class OpenAIStreamingInterface:
274
276
  # Store the ID of the tool call so allow skipping the corresponding response
275
277
  if self.function_id_buffer:
276
278
  self.prev_assistant_message_id = self.function_id_buffer
279
+ # Reset message reader at the start of a new send_message stream
280
+ self.assistant_message_json_reader.reset()
277
281
 
278
282
  else:
279
283
  if prev_message_type and prev_message_type != "tool_call_message":
@@ -328,39 +332,15 @@ class OpenAIStreamingInterface:
328
332
  self.last_flushed_function_name is not None
329
333
  and self.last_flushed_function_name == self.assistant_message_tool_name
330
334
  ):
331
- # do an additional parse on the updates_main_json
332
- if self.function_args_buffer:
333
- updates_main_json = self.function_args_buffer + updates_main_json
334
- self.function_args_buffer = None
335
-
336
- # Pretty gross hardcoding that assumes that if we're toggling into the keywords, we have the full prefix
337
- match_str = '{"' + self.assistant_message_tool_kwarg + '":"'
338
- if updates_main_json == match_str:
339
- updates_main_json = None
340
-
341
- else:
342
- # Some hardcoding to strip off the trailing "}"
343
- if updates_main_json in ["}", '"}']:
344
- updates_main_json = None
345
- if updates_main_json and len(updates_main_json) > 0 and updates_main_json[-1:] == '"':
346
- updates_main_json = updates_main_json[:-1]
347
-
348
- if not updates_main_json:
349
- # early exit to turn into content mode
350
- pass
351
-
352
- # There may be a buffer from a previous chunk, for example
353
- # if the previous chunk had arguments but we needed to flush name
354
- if self.function_args_buffer:
355
- # In this case, we should release the buffer + new data at once
356
- combined_chunk = self.function_args_buffer + updates_main_json
357
-
335
+ # Minimal, robust extraction: only emit the value of "message"
336
+ extracted = self.assistant_message_json_reader.process_json_chunk(tool_call.function.arguments)
337
+ if extracted:
358
338
  if prev_message_type and prev_message_type != "assistant_message":
359
339
  message_index += 1
360
340
  assistant_message = AssistantMessage(
361
341
  id=self.letta_message_id,
362
342
  date=datetime.now(timezone.utc),
363
- content=combined_chunk,
343
+ content=extracted,
364
344
  otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
365
345
  )
366
346
  prev_message_type = assistant_message.message_type
@@ -368,51 +348,6 @@ class OpenAIStreamingInterface:
368
348
  # Store the ID of the tool call so allow skipping the corresponding response
369
349
  if self.function_id_buffer:
370
350
  self.prev_assistant_message_id = self.function_id_buffer
371
- # clear buffer
372
- self.function_args_buffer = None
373
- self.function_id_buffer = None
374
-
375
- else:
376
- # If there's no buffer to clear, just output a new chunk with new data
377
- # TODO: THIS IS HORRIBLE
378
- # TODO: WE USE THE OLD JSON PARSER EARLIER (WHICH DOES NOTHING) AND NOW THE NEW JSON PARSER
379
- # TODO: THIS IS TOTALLY WRONG AND BAD, BUT SAVING FOR A LARGER REWRITE IN THE NEAR FUTURE
380
- parsed_args = self.optimistic_json_parser.parse(self.current_function_arguments)
381
-
382
- if parsed_args.get(self.assistant_message_tool_kwarg) and parsed_args.get(
383
- self.assistant_message_tool_kwarg
384
- ) != self.current_json_parse_result.get(self.assistant_message_tool_kwarg):
385
- new_content = parsed_args.get(self.assistant_message_tool_kwarg)
386
- prev_content = self.current_json_parse_result.get(self.assistant_message_tool_kwarg, "")
387
- # TODO: Assumes consistent state and that prev_content is subset of new_content
388
- diff = new_content.replace(prev_content, "", 1)
389
-
390
- # quick patch to mitigate double message streaming error
391
- # TODO: root cause this issue and remove patch
392
- if diff != "" and "\\n" not in new_content:
393
- converted_new_content = new_content.replace("\n", "\\n")
394
- converted_content_diff = converted_new_content.replace(prev_content, "", 1)
395
- if converted_content_diff == "":
396
- diff = converted_content_diff
397
-
398
- self.current_json_parse_result = parsed_args
399
- if prev_message_type and prev_message_type != "assistant_message":
400
- message_index += 1
401
- assistant_message = AssistantMessage(
402
- id=self.letta_message_id,
403
- date=datetime.now(timezone.utc),
404
- content=diff,
405
- # name=name,
406
- otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
407
- )
408
- prev_message_type = assistant_message.message_type
409
- yield assistant_message
410
-
411
- # Store the ID of the tool call so allow skipping the corresponding response
412
- if self.function_id_buffer:
413
- self.prev_assistant_message_id = self.function_id_buffer
414
- # clear buffers
415
- self.function_id_buffer = None
416
351
  else:
417
352
  # There may be a buffer from a previous chunk, for example
418
353
  # if the previous chunk had arguments but we needed to flush name
@@ -67,6 +67,7 @@ class GoogleVertexClient(LLMClientBase):
67
67
  # https://github.com/googleapis/python-aiplatform/issues/4472
68
68
  retry_count = 1
69
69
  should_retry = True
70
+ response_data = None
70
71
  while should_retry and retry_count <= self.MAX_RETRIES:
71
72
  try:
72
73
  response = await client.aio.models.generate_content(
@@ -79,6 +80,8 @@ class GoogleVertexClient(LLMClientBase):
79
80
  if e.code == 503 or e.code == 500:
80
81
  logger.warning(f"Received {e}, retrying {retry_count}/{self.MAX_RETRIES}")
81
82
  retry_count += 1
83
+ if retry_count > self.MAX_RETRIES:
84
+ raise e
82
85
  continue
83
86
  raise e
84
87
  except Exception as e:
@@ -114,6 +117,8 @@ class GoogleVertexClient(LLMClientBase):
114
117
  should_retry = is_malformed_function_call
115
118
  retry_count += 1
116
119
 
120
+ if response_data is None:
121
+ raise RuntimeError("Failed to get response data after all retries")
117
122
  return response_data
118
123
 
119
124
  @staticmethod
@@ -198,14 +198,15 @@ class OpenAIClient(LLMClientBase):
198
198
  # TODO(matt) move into LLMConfig
199
199
  # TODO: This vllm checking is very brittle and is a patch at most
200
200
  tool_choice = None
201
- if self.requires_auto_tool_choice(llm_config):
202
- tool_choice = "auto"
203
- elif tools:
204
- # only set if tools is non-Null
205
- tool_choice = "required"
206
-
207
- if force_tool_call is not None:
208
- tool_choice = ToolFunctionChoice(type="function", function=ToolFunctionChoiceFunctionCall(name=force_tool_call))
201
+ if tools: # only set tool_choice if tools exist
202
+ if self.requires_auto_tool_choice(llm_config):
203
+ tool_choice = "auto"
204
+ else:
205
+ # only set if tools is non-Null
206
+ tool_choice = "required"
207
+
208
+ if force_tool_call is not None:
209
+ tool_choice = ToolFunctionChoice(type="function", function=ToolFunctionChoiceFunctionCall(name=force_tool_call))
209
210
 
210
211
  data = ChatCompletionRequest(
211
212
  model=model,
letta/orm/job.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from datetime import datetime
2
2
  from typing import TYPE_CHECKING, List, Optional
3
3
 
4
- from sqlalchemy import JSON, BigInteger, Index, String
4
+ from sqlalchemy import JSON, BigInteger, ForeignKey, Index, String
5
5
  from sqlalchemy.orm import Mapped, mapped_column, relationship
6
6
 
7
7
  from letta.orm.mixins import UserMixin
@@ -12,6 +12,7 @@ from letta.schemas.job import Job as PydanticJob, LettaRequestConfig
12
12
  if TYPE_CHECKING:
13
13
  from letta.orm.job_messages import JobMessage
14
14
  from letta.orm.message import Message
15
+ from letta.orm.organization import Organization
15
16
  from letta.orm.step import Step
16
17
  from letta.orm.user import User
17
18
 
@@ -36,6 +37,7 @@ class Job(SqlalchemyBase, UserMixin):
36
37
  request_config: Mapped[Optional[LettaRequestConfig]] = mapped_column(
37
38
  JSON, nullable=True, doc="The request configuration for the job, stored as JSON."
38
39
  )
40
+ organization_id: Mapped[Optional[str]] = mapped_column(String, ForeignKey("organizations.id"))
39
41
 
40
42
  # callback related columns
41
43
  callback_url: Mapped[Optional[str]] = mapped_column(String, nullable=True, doc="When set, POST to this URL after job completion.")
@@ -53,6 +55,8 @@ class Job(SqlalchemyBase, UserMixin):
53
55
  user: Mapped["User"] = relationship("User", back_populates="jobs")
54
56
  job_messages: Mapped[List["JobMessage"]] = relationship("JobMessage", back_populates="job", cascade="all, delete-orphan")
55
57
  steps: Mapped[List["Step"]] = relationship("Step", back_populates="job", cascade="save-update")
58
+ # organization relationship (nullable for backward compatibility)
59
+ organization: Mapped[Optional["Organization"]] = relationship("Organization", back_populates="jobs")
56
60
 
57
61
  @property
58
62
  def messages(self) -> List["Message"]:
letta/orm/organization.py CHANGED
@@ -12,6 +12,7 @@ if TYPE_CHECKING:
12
12
  from letta.orm.block import Block
13
13
  from letta.orm.group import Group
14
14
  from letta.orm.identity import Identity
15
+ from letta.orm.job import Job
15
16
  from letta.orm.llm_batch_items import LLMBatchItem
16
17
  from letta.orm.llm_batch_job import LLMBatchJob
17
18
  from letta.orm.message import Message
@@ -66,3 +67,4 @@ class Organization(SqlalchemyBase):
66
67
  llm_batch_items: Mapped[List["LLMBatchItem"]] = relationship(
67
68
  "LLMBatchItem", back_populates="organization", cascade="all, delete-orphan"
68
69
  )
70
+ jobs: Mapped[List["Job"]] = relationship("Job", back_populates="organization", cascade="all, delete-orphan")
@@ -8,6 +8,9 @@ from typing import AsyncIterator, Dict, List, Optional
8
8
 
9
9
  from letta.data_sources.redis_client import AsyncRedisClient
10
10
  from letta.log import get_logger
11
+ from letta.schemas.enums import JobStatus
12
+ from letta.schemas.user import User
13
+ from letta.services.job_manager import JobManager
11
14
  from letta.utils import safe_create_task
12
15
 
13
16
  logger = get_logger(__name__)
@@ -133,9 +136,9 @@ class RedisSSEStreamWriter:
133
136
 
134
137
  async with client.pipeline(transaction=False) as pipe:
135
138
  for chunk in chunks:
136
- pipe.xadd(stream_key, chunk, maxlen=self.max_stream_length, approximate=True)
139
+ await pipe.xadd(stream_key, chunk, maxlen=self.max_stream_length, approximate=True)
137
140
 
138
- pipe.expire(stream_key, self.stream_ttl)
141
+ await pipe.expire(stream_key, self.stream_ttl)
139
142
 
140
143
  await pipe.execute()
141
144
 
@@ -191,6 +194,8 @@ async def create_background_stream_processor(
191
194
  redis_client: AsyncRedisClient,
192
195
  run_id: str,
193
196
  writer: Optional[RedisSSEStreamWriter] = None,
197
+ job_manager: Optional[JobManager] = None,
198
+ actor: Optional[User] = None,
194
199
  ) -> None:
195
200
  """
196
201
  Process a stream in the background and store chunks to Redis.
@@ -203,6 +208,8 @@ async def create_background_stream_processor(
203
208
  redis_client: Redis client instance
204
209
  run_id: The run ID to store chunks under
205
210
  writer: Optional pre-configured writer (creates new if not provided)
211
+ job_manager: Optional job manager for updating job status
212
+ actor: Optional actor for job status updates
206
213
  """
207
214
  if writer is None:
208
215
  writer = RedisSSEStreamWriter(redis_client)
@@ -227,6 +234,12 @@ async def create_background_stream_processor(
227
234
  logger.error(f"Error processing stream for run {run_id}: {e}")
228
235
  # Write error chunk
229
236
  # error_chunk = {"error": {"message": str(e)}}
237
+ # Mark run_id terminal state
238
+ if job_manager and actor:
239
+ await job_manager.safe_update_job_status_async(
240
+ job_id=run_id, new_status=JobStatus.failed, actor=actor, metadata={"error": str(e)}
241
+ )
242
+
230
243
  error_chunk = {"error": str(e), "code": "INTERNAL_SERVER_ERROR"}
231
244
  await writer.write_chunk(run_id=run_id, data=f"event: error\ndata: {json.dumps(error_chunk)}\n\n", is_complete=True)
232
245
  finally:
@@ -1348,6 +1348,8 @@ async def send_message_streaming(
1348
1348
  stream_generator=raw_stream,
1349
1349
  redis_client=redis_client,
1350
1350
  run_id=run.id,
1351
+ job_manager=server.job_manager,
1352
+ actor=actor,
1351
1353
  ),
1352
1354
  label=f"background_stream_processor_{run.id}",
1353
1355
  )
@@ -12,7 +12,7 @@ from composio.exceptions import (
12
12
  EnumStringNotFound,
13
13
  )
14
14
  from fastapi import APIRouter, Body, Depends, Header, HTTPException, Query, Request
15
- from httpx import HTTPStatusError
15
+ from httpx import ConnectError, HTTPStatusError
16
16
  from pydantic import BaseModel, Field
17
17
  from starlette.responses import StreamingResponse
18
18
 
@@ -151,7 +151,6 @@ async def count_tools(
151
151
  exclude_letta_tools=exclude_letta_tools,
152
152
  )
153
153
  except Exception as e:
154
- print(f"Error occurred: {e}")
155
154
  raise HTTPException(status_code=500, detail=str(e))
156
155
 
157
156
 
@@ -265,8 +264,6 @@ async def list_tools(
265
264
  return_only_letta_tools=return_only_letta_tools,
266
265
  )
267
266
  except Exception as e:
268
- # Log or print the full exception here for debugging
269
- print(f"Error occurred: {e}")
270
267
  raise HTTPException(status_code=500, detail=str(e))
271
268
 
272
269
 
@@ -284,21 +281,13 @@ async def create_tool(
284
281
  tool = Tool(**request.model_dump(exclude_unset=True))
285
282
  return await server.tool_manager.create_tool_async(pydantic_tool=tool, actor=actor)
286
283
  except UniqueConstraintViolationError as e:
287
- # Log or print the full exception here for debugging
288
- print(f"Error occurred: {e}")
289
284
  clean_error_message = "Tool with this name already exists."
290
285
  raise HTTPException(status_code=409, detail=clean_error_message)
291
286
  except LettaToolCreateError as e:
292
287
  # HTTP 400 == Bad Request
293
- print(f"Error occurred during tool creation: {e}")
294
- # print the full stack trace
295
- import traceback
296
-
297
- print(traceback.format_exc())
298
288
  raise HTTPException(status_code=400, detail=str(e))
299
289
  except Exception as e:
300
290
  # Catch other unexpected errors and raise an internal server error
301
- print(f"Unexpected error occurred: {e}")
302
291
  raise HTTPException(status_code=500, detail=f"An unexpected error occurred: {str(e)}")
303
292
 
304
293
 
@@ -319,15 +308,12 @@ async def upsert_tool(
319
308
  return tool
320
309
  except UniqueConstraintViolationError as e:
321
310
  # Log the error and raise a conflict exception
322
- print(f"Unique constraint violation occurred: {e}")
323
311
  raise HTTPException(status_code=409, detail=str(e))
324
312
  except LettaToolCreateError as e:
325
313
  # HTTP 400 == Bad Request
326
- print(f"Error occurred during tool upsert: {e}")
327
314
  raise HTTPException(status_code=400, detail=str(e))
328
315
  except Exception as e:
329
316
  # Catch other unexpected errors and raise an internal server error
330
- print(f"Unexpected error occurred: {e}")
331
317
  raise HTTPException(status_code=500, detail=f"An unexpected error occurred: {str(e)}")
332
318
 
333
319
 
@@ -344,7 +330,6 @@ async def modify_tool(
344
330
  try:
345
331
  actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
346
332
  tool = await server.tool_manager.update_tool_by_id_async(tool_id=tool_id, tool_update=request, actor=actor)
347
- print("FINAL TOOL", tool)
348
333
  return tool
349
334
  except LettaToolNameConflictError as e:
350
335
  # HTTP 409 == Conflict
@@ -394,16 +379,10 @@ async def run_tool_from_source(
394
379
  )
395
380
  except LettaToolCreateError as e:
396
381
  # HTTP 400 == Bad Request
397
- print(f"Error occurred during tool creation: {e}")
398
- # print the full stack trace
399
- import traceback
400
-
401
- print(traceback.format_exc())
402
382
  raise HTTPException(status_code=400, detail=str(e))
403
383
 
404
384
  except Exception as e:
405
385
  # Catch other unexpected errors and raise an internal server error
406
- print(f"Unexpected error occurred: {e}")
407
386
  raise HTTPException(status_code=500, detail=f"An unexpected error occurred: {str(e)}")
408
387
 
409
388
 
@@ -559,32 +538,38 @@ async def list_mcp_tools_by_server(
559
538
  """
560
539
  Get a list of all tools for a specific MCP server
561
540
  """
562
- if tool_settings.mcp_read_from_config:
563
- try:
564
- return await server.get_tools_from_mcp_server(mcp_server_name=mcp_server_name)
565
- except ValueError as e:
566
- # ValueError means that the MCP server name doesn't exist
541
+ try:
542
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
543
+ mcp_tools = await server.mcp_manager.list_mcp_server_tools(mcp_server_name=mcp_server_name, actor=actor)
544
+ return mcp_tools
545
+ except Exception as e:
546
+ if isinstance(e, ConnectError) or isinstance(e, ConnectionError):
567
547
  raise HTTPException(
568
- status_code=400, # Bad Request
548
+ status_code=404,
569
549
  detail={
570
- "code": "MCPServerNotFoundError",
550
+ "code": "MCPListToolsError",
571
551
  "message": str(e),
572
552
  "mcp_server_name": mcp_server_name,
573
553
  },
574
554
  )
575
- except MCPTimeoutError as e:
555
+ if isinstance(e, HTTPStatusError):
576
556
  raise HTTPException(
577
- status_code=408, # Timeout
557
+ status_code=401,
578
558
  detail={
579
- "code": "MCPTimeoutError",
559
+ "code": "MCPListToolsError",
560
+ "message": str(e),
561
+ "mcp_server_name": mcp_server_name,
562
+ },
563
+ )
564
+ else:
565
+ raise HTTPException(
566
+ status_code=500,
567
+ detail={
568
+ "code": "MCPListToolsError",
580
569
  "message": str(e),
581
570
  "mcp_server_name": mcp_server_name,
582
571
  },
583
572
  )
584
- else:
585
- actor = await server.user_manager.get_actor_or_default_async(actor_id=actor_id)
586
- mcp_tools = await server.mcp_manager.list_mcp_server_tools(mcp_server_name=mcp_server_name, actor=actor)
587
- return mcp_tools
588
573
 
589
574
 
590
575
  @router.post("/mcp/servers/{mcp_server_name}/resync", operation_id="resync_mcp_server_tools")
@@ -753,7 +738,8 @@ async def add_mcp_server_to_config(
753
738
  custom_headers=request.custom_headers,
754
739
  )
755
740
 
756
- await server.mcp_manager.create_mcp_server(mapped_request, actor=actor)
741
+ # Create MCP server and optimistically sync tools
742
+ await server.mcp_manager.create_mcp_server_with_tools(mapped_request, actor=actor)
757
743
 
758
744
  # TODO: don't do this in the future (just return MCPServer)
759
745
  all_servers = await server.mcp_manager.list_mcp_servers(actor=actor)
@@ -769,7 +755,6 @@ async def add_mcp_server_to_config(
769
755
  },
770
756
  )
771
757
  except Exception as e:
772
- print(f"Unexpected error occurred while adding MCP server: {e}")
773
758
  raise HTTPException(status_code=500, detail=f"An unexpected error occurred: {str(e)}")
774
759
 
775
760
 
@@ -801,7 +786,6 @@ async def update_mcp_server(
801
786
  # Re-raise HTTP exceptions (like 404)
802
787
  raise
803
788
  except Exception as e:
804
- print(f"Unexpected error occurred while updating MCP server: {e}")
805
789
  raise HTTPException(status_code=500, detail=f"An unexpected error occurred: {str(e)}")
806
790
 
807
791
 
@@ -43,6 +43,7 @@ class JobManager:
43
43
  pydantic_job.user_id = actor.id
44
44
  job_data = pydantic_job.model_dump(to_orm=True)
45
45
  job = JobModel(**job_data)
46
+ job.organization_id = actor.organization_id
46
47
  job.create(session, actor=actor) # Save job in the database
47
48
  return job.to_pydantic()
48
49
 
@@ -57,6 +58,7 @@ class JobManager:
57
58
  pydantic_job.user_id = actor.id
58
59
  job_data = pydantic_job.model_dump(to_orm=True)
59
60
  job = JobModel(**job_data)
61
+ job.organization_id = actor.organization_id
60
62
  job = await job.create_async(session, actor=actor, no_commit=True, no_refresh=True) # Save job in the database
61
63
  result = job.to_pydantic()
62
64
  await session.commit()
@@ -150,8 +152,9 @@ class JobManager:
150
152
  logger.error(f"Invalid job status transition from {current_status} to {job_update.status} for job {job_id}")
151
153
  raise ValueError(f"Invalid job status transition from {current_status} to {job_update.status}")
152
154
 
153
- # Check if we'll need to dispatch callback
154
- if job_update.status in {JobStatus.completed, JobStatus.failed} and job.callback_url:
155
+ # Check if we'll need to dispatch callback (only if not already completed)
156
+ not_completed_before = not bool(job.completed_at)
157
+ if job_update.status in {JobStatus.completed, JobStatus.failed} and not_completed_before and job.callback_url:
155
158
  needs_callback = True
156
159
  callback_url = job.callback_url
157
160
 
@@ -79,11 +79,16 @@ class MCPManager:
79
79
  except Exception as e:
80
80
  # MCP tool listing errors are often due to connection/configuration issues, not system errors
81
81
  # Log at info level to avoid triggering Sentry alerts for expected failures
82
- logger.info(f"Error listing tools for MCP server {mcp_server_name}: {e}")
83
- return []
82
+ logger.warning(f"Error listing tools for MCP server {mcp_server_name}: {e}")
83
+ raise e
84
84
  finally:
85
85
  if mcp_client:
86
- await mcp_client.cleanup()
86
+ try:
87
+ await mcp_client.cleanup()
88
+ except* Exception as eg:
89
+ for e in eg.exceptions:
90
+ logger.warning(f"Error listing tools for MCP server {mcp_server_name}: {e}")
91
+ raise e
87
92
 
88
93
  @enforce_types
89
94
  async def execute_mcp_server_tool(
@@ -349,6 +354,62 @@ class MCPManager:
349
354
  logger.error(f"Failed to create MCP server: {e}")
350
355
  raise
351
356
 
357
+ @enforce_types
358
+ async def create_mcp_server_with_tools(self, pydantic_mcp_server: MCPServer, actor: PydanticUser) -> MCPServer:
359
+ """
360
+ Create a new MCP server and optimistically sync its tools.
361
+
362
+ This method:
363
+ 1. Creates the MCP server record
364
+ 2. Attempts to connect and fetch tools
365
+ 3. Persists valid tools in parallel (best-effort)
366
+ """
367
+ import asyncio
368
+
369
+ # First, create the MCP server
370
+ created_server = await self.create_mcp_server(pydantic_mcp_server, actor)
371
+
372
+ # Optimistically try to sync tools
373
+ try:
374
+ logger.info(f"Attempting to auto-sync tools from MCP server: {created_server.server_name}")
375
+
376
+ # List all tools from the MCP server
377
+ mcp_tools = await self.list_mcp_server_tools(mcp_server_name=created_server.server_name, actor=actor)
378
+
379
+ # Filter out invalid tools
380
+ valid_tools = [tool for tool in mcp_tools if not (tool.health and tool.health.status == "INVALID")]
381
+
382
+ # Register in parallel
383
+ if valid_tools:
384
+ tool_tasks = []
385
+ for mcp_tool in valid_tools:
386
+ tool_create = ToolCreate.from_mcp(mcp_server_name=created_server.server_name, mcp_tool=mcp_tool)
387
+ task = self.tool_manager.create_mcp_tool_async(
388
+ tool_create=tool_create, mcp_server_name=created_server.server_name, mcp_server_id=created_server.id, actor=actor
389
+ )
390
+ tool_tasks.append(task)
391
+
392
+ results = await asyncio.gather(*tool_tasks, return_exceptions=True)
393
+
394
+ successful = sum(1 for r in results if not isinstance(r, Exception))
395
+ failed = len(results) - successful
396
+ logger.info(
397
+ f"Auto-sync completed for MCP server {created_server.server_name}: "
398
+ f"{successful} tools persisted, {failed} failed, "
399
+ f"{len(mcp_tools) - len(valid_tools)} invalid tools skipped"
400
+ )
401
+ else:
402
+ logger.info(f"No valid tools found to sync from MCP server {created_server.server_name}")
403
+
404
+ except Exception as e:
405
+ # Log the error but don't fail the server creation
406
+ logger.warning(
407
+ f"Failed to auto-sync tools from MCP server {created_server.server_name}: {e}. "
408
+ f"Server was created successfully but tools were not persisted."
409
+ )
410
+
411
+ return created_server
412
+
352
413
  @enforce_types
353
414
  async def update_mcp_server_by_id(self, mcp_server_id: str, mcp_server_update: UpdateMCPServer, actor: PydanticUser) -> MCPServer:
354
415
  """Update a tool by its ID with the given ToolUpdate object."""
@@ -645,7 +645,7 @@ class LettaFileToolExecutor(ToolExecutor):
645
645
  raise e
646
646
 
647
647
  if not files_with_matches:
648
- return f"No semantic matches found in Turbopuffer for query: '{query}'"
648
+ return f"No semantic matches found for query: '{query}'"
649
649
 
650
650
  # Format results
651
651
  passage_num = 0
@@ -678,7 +678,7 @@ class LettaFileToolExecutor(ToolExecutor):
678
678
 
679
679
  # create summary header
680
680
  file_count = len(files_with_matches)
681
- summary = f"Found {total_hits} Turbopuffer matches in {file_count} file{'s' if file_count != 1 else ''} for query: '{query}'"
681
+ summary = f"Found {total_hits} matches in {file_count} file{'s' if file_count != 1 else ''} for query: '{query}'"
682
682
 
683
683
  # combine all results
684
684
  formatted_results = [summary, "=" * len(summary)] + results
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: letta-nightly
3
- Version: 0.11.7.dev20250911104039
3
+ Version: 0.11.7.dev20250912104045
4
4
  Summary: Create LLM agents with long-term memory and custom tools
5
5
  Author-email: Letta Team <contact@letta.com>
6
6
  License: Apache License
@@ -84,7 +84,7 @@ Requires-Dist: magika>=0.6.2; extra == 'desktop'
84
84
  Requires-Dist: pgvector>=0.2.3; extra == 'desktop'
85
85
  Requires-Dist: sqlite-vec>=0.1.7a2; extra == 'desktop'
86
86
  Requires-Dist: tiktoken>=0.11.0; extra == 'desktop'
87
- Requires-Dist: uvicorn>=0.24.0.post1; extra == 'desktop'
87
+ Requires-Dist: uvicorn==0.29.0; extra == 'desktop'
88
88
  Requires-Dist: websockets; extra == 'desktop'
89
89
  Requires-Dist: wikipedia>=1.4.0; extra == 'desktop'
90
90
  Provides-Extra: dev
@@ -125,7 +125,7 @@ Provides-Extra: redis
125
125
  Requires-Dist: redis>=6.2.0; extra == 'redis'
126
126
  Provides-Extra: server
127
127
  Requires-Dist: fastapi>=0.115.6; extra == 'server'
128
- Requires-Dist: uvicorn>=0.24.0.post1; extra == 'server'
128
+ Requires-Dist: uvicorn==0.29.0; extra == 'server'
129
129
  Requires-Dist: websockets; extra == 'server'
130
130
  Provides-Extra: sqlite
131
131
  Requires-Dist: aiosqlite>=0.21.0; extra == 'sqlite'
@@ -27,7 +27,7 @@ letta/agents/exceptions.py,sha256=BQY4D4w32OYHM63CM19ko7dPwZiAzUs3NbKvzmCTcJg,31
27
27
  letta/agents/helpers.py,sha256=eCHsvZEkTe0L_uZHYkfNAztsEJW0FTnKZMgVbqlI0Yg,11618
28
28
  letta/agents/letta_agent.py,sha256=6nRTh5kzUpqK7eNMk4DlcgEoPmDxFmRb5ysoVHa-vh8,99488
29
29
  letta/agents/letta_agent_batch.py,sha256=17RpYVXpGh9dlKxdMOLMCOHWFsi6N5S9FJHxooxkJCI,27998
30
- letta/agents/letta_agent_v2.py,sha256=EDQ7ShQXS09cCMTMj0i3J2eECp5-ISDrB4QQWT9ALlg,58257
30
+ letta/agents/letta_agent_v2.py,sha256=Xs54mewx9SgHHFAz8uLJ_6OHv9RHU1PtkwAB_Pu0XMk,58992
31
31
  letta/agents/voice_agent.py,sha256=y-n6qadfKsswvGODzXH02pLIQQ44wnaDSE6oUgKHVkA,23381
32
32
  letta/agents/voice_sleeptime_agent.py,sha256=_JzCbWBOKrmo1cTaqZFTrQudpJEapwAyrXYtAHUILGo,8675
33
33
  letta/cli/cli.py,sha256=tKtghlX36Rp0_HbkMosvlAapL07JXhA0vKLGTNKnxSQ,1615
@@ -78,14 +78,14 @@ letta/helpers/reasoning_helper.py,sha256=8P5AJo-UFsYhZC3yNx4N-pIkVUsjfz7ZPmydsVK
78
78
  letta/helpers/singleton.py,sha256=Y4dG_ZBCcrogvl9iZ69bSLq-QltrdP8wHqKkhef8OBI,370
79
79
  letta/helpers/tool_execution_helper.py,sha256=Oz9xNDrSFUIrYhEhLaw8yoXdHbfijuxwtS1tJv-lH2A,5149
80
80
  letta/helpers/tool_rule_solver.py,sha256=dd5gvj67_8FgXrC0_Px5TWXE8A9CMFvH_E26idPrkKY,9848
81
- letta/helpers/tpuf_client.py,sha256=dZuR5ZZqWOUYwXS4aX5YDEkw1Wf3zo_r5hpL8x7xFig,60993
81
+ letta/helpers/tpuf_client.py,sha256=7x6dP7MeHfINs_aVxQAmEXYYM3bJcw3fiyrJYG1ZpAk,62211
82
82
  letta/humans/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
83
83
  letta/humans/examples/basic.txt,sha256=Lcp8YESTWvOJgO4Yf_yyQmgo5bKakeB1nIVrwEGG6PA,17
84
84
  letta/humans/examples/cs_phd.txt,sha256=9C9ZAV_VuG7GB31ksy3-_NAyk8rjE6YtVOkhp08k1xw,297
85
85
  letta/interfaces/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
86
86
  letta/interfaces/anthropic_streaming_interface.py,sha256=0VyK8kTRgCLNDLQN6vX1gJ0dfJhqguL_NL1GYgFr6fU,25614
87
87
  letta/interfaces/openai_chat_completions_streaming_interface.py,sha256=3xHXh8cW79EkiMUTYfvcH_s92nkLjxXfvtVOVC3bfLo,5050
88
- letta/interfaces/openai_streaming_interface.py,sha256=m2QeZo4w5lclwTypqAcHOJ4disaHqwb9yeqQaukfesA,28157
88
+ letta/interfaces/openai_streaming_interface.py,sha256=t_TKcZSH0Bv_ajOh2mTd4RetrCr-rahkjmGIZIIGDXQ,23593
89
89
  letta/interfaces/utils.py,sha256=c6jvO0dBYHh8DQnlN-B0qeNC64d3CSunhfqlFA4pJTY,278
90
90
  letta/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
91
91
  letta/jobs/helpers.py,sha256=kO4aj954xsQ1RAmkjY6LQQ7JEIGuhaxB1e9pzrYKHAY,914
@@ -99,7 +99,7 @@ letta/llm_api/bedrock_client.py,sha256=gNKSFGCbrrLMPvtBItAOz1nme4K_opgkZdFa3cUzp
99
99
  letta/llm_api/deepseek_client.py,sha256=di6ApSQu1DewXw0_JIP7AK4IHvXQHd0e32tQfFf5F34,16975
100
100
  letta/llm_api/google_ai_client.py,sha256=JweTUHZXvK6kcZBGXA7XEU53KP4vM7_zdD7AorCtsdI,8166
101
101
  letta/llm_api/google_constants.py,sha256=eOjOv-FImyJ4b4QGIaod-mEROMtrBFz0yhuYHqOEkwY,797
102
- letta/llm_api/google_vertex_client.py,sha256=PUTUa2DM-nIVhE7H3pDwPqgPl2gLAAgGgANzABV6Xfc,28680
102
+ letta/llm_api/google_vertex_client.py,sha256=57qrBe5dY-ERB9xI9_tWRwW_uSxGbHqR02mvnWmCLGY,28910
103
103
  letta/llm_api/groq_client.py,sha256=nNeWSgDVOLn3iFiicDKyhHj7f73JxrB9-7_M2Pv2e1I,3192
104
104
  letta/llm_api/helpers.py,sha256=GXV_SuaU7uSCDj6bxDcCCF7CUjuZQCVWd5qZ3OsHVNk,17587
105
105
  letta/llm_api/llm_api_tools.py,sha256=lsZ6OeIHesyOfbNQi5CVw5hn1lTQP5gJyforp-D0nk8,12294
@@ -107,7 +107,7 @@ letta/llm_api/llm_client.py,sha256=iXiPbrhluP2DBczv9nkFlAXdwWGOkg0lNDA9LzLrG4o,3
107
107
  letta/llm_api/llm_client_base.py,sha256=RFo8H4ILxVyzB3DeF4rJoJJYjRF8ScVO4yyDrhuN0DY,10052
108
108
  letta/llm_api/mistral.py,sha256=ruOTBt07Uzx7S30_eXhedVWngtpjtlzG6Ox1Iw0_mQs,662
109
109
  letta/llm_api/openai.py,sha256=56cwdS9l-75cMTtY9df6Dbb1M9crH8YQsSdF3Pm3Rpg,27393
110
- letta/llm_api/openai_client.py,sha256=rnWMspbJ7ZcpFPhIJDBNACRJPk_Y3v8DtRzSMbl76ZU,22396
110
+ letta/llm_api/openai_client.py,sha256=Ww68D103uQolsALOzfPD5-CTuEaIFBbkdnrtMBIaZlc,22475
111
111
  letta/llm_api/together_client.py,sha256=HeDMDDa525yfDTKciODDfX_t93QBfFmX0n2P-FT1QTU,2284
112
112
  letta/llm_api/xai_client.py,sha256=3mpSQ9OoWyjqo2VhNM_m0EPBzS69r4p-OEwL7UWc9oY,3772
113
113
  letta/llm_api/sample_response_jsons/aws_bedrock.json,sha256=RS3VqyxPB9hQQCPm42hWoga0bisKv_0e8ZF-c3Ag1FA,930
@@ -171,7 +171,7 @@ letta/orm/groups_blocks.py,sha256=ou18XqI9tkb0fcecUd4eHTVmmndGuby1DIdmHM5lHF4,48
171
171
  letta/orm/identities_agents.py,sha256=cfIQ6UsbwmjxtGVmFt1ArK2zbKr9k6VWoELuISDnLSc,502
172
172
  letta/orm/identities_blocks.py,sha256=oS0DnDXKzcWtlH2dDFREkNF1JHJ3Kyg8fN9GI8DKtcg,501
173
173
  letta/orm/identity.py,sha256=NRgC6ArQZCrs3LYhGHoBMHNJzi_uoYFvJAr0HoRhqLg,2924
174
- letta/orm/job.py,sha256=kTRnIWBjfwt0fSKmu3XPC1sPpjVukWJx672PRhJrQG0,3019
174
+ letta/orm/job.py,sha256=nlDw6Y7zT96jFW-OOjf-vY66Py7S6_3jTRmhOmNnIUw,3357
175
175
  letta/orm/job_messages.py,sha256=SgwaYPYwwAC3dBtl9Xye_TWUl9H_-m95S95TTcfPyOg,1245
176
176
  letta/orm/llm_batch_items.py,sha256=LZI9vjOrswiGXPVLvLOT5uObDtyTXX1p7yljSiiMH7g,2725
177
177
  letta/orm/llm_batch_job.py,sha256=LaeOrnNf6FMm6ff2kOCEAjtbSuz4C5KYU5OmM90F1PY,2368
@@ -179,7 +179,7 @@ letta/orm/mcp_oauth.py,sha256=lr0XIcj9zwQpWlqQHIWAaT73YnunsrkEnLzBLP-8L2k,3095
179
179
  letta/orm/mcp_server.py,sha256=PT3Edqn0Er1ZDExsXdjhC--iGL4Vi3XjPPgxNfbNRDA,2155
180
180
  letta/orm/message.py,sha256=sGMH7XJRErtIEngFV08b9A_zk5veyLkpQsU3zKN0AhM,9335
181
181
  letta/orm/mixins.py,sha256=moZyS4e9gXGULKNsOqQuZrBn55IlrFF7MQOAVl68Aq0,2688
182
- letta/orm/organization.py,sha256=g7d4rFMdVM3r7TwHnx_uSCKLoOw5kXzAgKd2bPr7S6U,4058
182
+ letta/orm/organization.py,sha256=wZ3yvPa6Vy-c_74S_XGjzjWND1oR7U7xzrdrdipZM8A,4205
183
183
  letta/orm/passage.py,sha256=qjBZdyZV05ZGe6Dprn4GIwdc3wjYRuchZO2Ja9I9bT4,4404
184
184
  letta/orm/passage_tag.py,sha256=TtT00DjXh9n0BshehBBcTjo3lnR1d-wNBi-ePJTZ_0M,2121
185
185
  letta/orm/prompt.py,sha256=NpFPTm3jD8Aewxhlnq8s4eIzANJ3bAEtbq6UmFqyc3U,489
@@ -342,7 +342,7 @@ letta/server/rest_api/auth_token.py,sha256=725EFEIiNj4dh70hrSd94UysmFD8vcJLrTRfN
342
342
  letta/server/rest_api/chat_completions_interface.py,sha256=-7wO7pNBWXMqblVkJpuZ8JPJ-LjudLTtT6BJu-q_XAM,11138
343
343
  letta/server/rest_api/interface.py,sha256=X5NZ8oerDcipG9y1AfD92zJ_2TgVMO4eJ42RP82GFF8,70952
344
344
  letta/server/rest_api/json_parser.py,sha256=yoakaCkSMdf0Y_pyILoFKZlvzXeqF-E1KNeHzatLMDc,9157
345
- letta/server/rest_api/redis_stream_manager.py,sha256=YR9daW5kG-s31-lyg4YVPU8lClS6UG7zuzZWKVUXBQg,10179
345
+ letta/server/rest_api/redis_stream_manager.py,sha256=hz85CigFWdLkK1FWUmF-i6ObgoKkuoEgkiwshZ6QPKI,10764
346
346
  letta/server/rest_api/static_files.py,sha256=NG8sN4Z5EJ8JVQdj19tkFa9iQ1kBPTab9f_CUxd_u4Q,3143
347
347
  letta/server/rest_api/streaming_response.py,sha256=wfhby6skucjGtw9d9pcfa856lI1r6JKaosCYbutKD2k,14458
348
348
  letta/server/rest_api/utils.py,sha256=IT3RnZJWNIaTdFEEyveZb47o9PIzDT_2pIgpPBVN7iU,19326
@@ -355,7 +355,7 @@ letta/server/rest_api/routers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5N
355
355
  letta/server/rest_api/routers/openai/chat_completions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
356
356
  letta/server/rest_api/routers/openai/chat_completions/chat_completions.py,sha256=ohM1i8BsNxTiw8duuRT5X_0tSUzBwctQM4fJ5DXURic,5157
357
357
  letta/server/rest_api/routers/v1/__init__.py,sha256=9MnEA7CgtIxyU_dDNG0jm-Ziqu1somBml-e5gKjgd9I,1997
358
- letta/server/rest_api/routers/v1/agents.py,sha256=VQyGsi6Ci7Ili8rSoKnwY9RZYBcSXzcj7lnpz4OopSk,75580
358
+ letta/server/rest_api/routers/v1/agents.py,sha256=2eo7EDXTpybTPfOvgEGhm81LenIJcXNxv-bf5CcqjkU,75673
359
359
  letta/server/rest_api/routers/v1/blocks.py,sha256=ykI77xnmIxPLqdAy5kzGyGw0w0ZRyVXn-O5Xcdj6-70,7690
360
360
  letta/server/rest_api/routers/v1/embeddings.py,sha256=PRaQlrmEXPiIdWsTbadrFsv3Afyv5oEFUdhgHA8FTi8,989
361
361
  letta/server/rest_api/routers/v1/folders.py,sha256=8Yb-bw2JdXBxMfrJNIZQk9_FKN2fet9Ccp8T83_c2sc,23539
@@ -374,7 +374,7 @@ letta/server/rest_api/routers/v1/sources.py,sha256=nXZxtHi40281VltWmx1RwGBbau_00
374
374
  letta/server/rest_api/routers/v1/steps.py,sha256=bTzfz1GR3VEZdJRYUGiSr6ZLd12i5faPsf3oAqu1eMk,5570
375
375
  letta/server/rest_api/routers/v1/tags.py,sha256=ef94QitUSJ3NQVffWF1ZqANUZ2b2jRyGHp_I3UUjhno,912
376
376
  letta/server/rest_api/routers/v1/telemetry.py,sha256=eSTg7mWbuwPb2OTHQxwRM0EUEl49wHzNB6i1xJtH8BQ,1036
377
- letta/server/rest_api/routers/v1/tools.py,sha256=aYp__U2PgTCpL3eypYUJ6V5C4iQA6LeFBHTSo8w-WOg,51890
377
+ letta/server/rest_api/routers/v1/tools.py,sha256=UMtJj3bX8fVe0VuuU5JS0TeaFimEzZ4YRyphSO2tQMU,51085
378
378
  letta/server/rest_api/routers/v1/users.py,sha256=J1vaTbS1UrBMgnPya7GdZ2wr3L9XHmkm6qdGY6pWaOI,2366
379
379
  letta/server/rest_api/routers/v1/voice.py,sha256=ghMBp5Uovbf0-3nN6d9P5kpl1hHACLRMhIDGQp96G9Q,1986
380
380
  letta/server/static_files/favicon.ico,sha256=DezhLdFSbM8o81wCOZcV3riq7tFUOGQD4h6-vr-HuU0,342
@@ -397,9 +397,9 @@ letta/services/file_manager.py,sha256=d4uX8RblmqNGk1MsfeGzQ5uDWKVFP-AH63Jz5xOkj2
397
397
  letta/services/files_agents_manager.py,sha256=QJrJTgDn3RXUjZIGiIw4GQ5k2iKj-Wvzs-WQetpQ154,30059
398
398
  letta/services/group_manager.py,sha256=dD4DDHjOptMrtbWqw1ErlhpBqChw2ubLJdILjeLTY8I,29183
399
399
  letta/services/identity_manager.py,sha256=JI9Xc7EsBagSwDS2na4rFNhoO_LuaxlkVO_1oIK_ITQ,11841
400
- letta/services/job_manager.py,sha256=31AKBwQWkjOFH2n4Cx3KCv1g_n499C_1VRIVviUGsLI,35312
400
+ letta/services/job_manager.py,sha256=nDrnr_r8ELwf8KMKyRRrWHsysrTGldgCTplJdaSiNiQ,35543
401
401
  letta/services/llm_batch_manager.py,sha256=iDzLFfmgpQooGY4zpN_w8q1SZ27fr2Cv6Ks3ltZErL8,20929
402
- letta/services/mcp_manager.py,sha256=aenw1-UgjecmQ6BLrTWVvpKJmeFcGslocdhRKT_PqEk,45077
402
+ letta/services/mcp_manager.py,sha256=QuvKQnwxMXrhiCaYlF50GZwXmbSU7PxmcOZ85sQ3t7I,47848
403
403
  letta/services/message_manager.py,sha256=tomsZidPT-I95sJsEsls-vj3qglehV7XNTs-m2zF8Bg,60629
404
404
  letta/services/organization_manager.py,sha256=JMW5oS_sr6vQQ27OgRV3BR1JL0MqyoGUDcpEOs3SLRY,5800
405
405
  letta/services/passage_manager.py,sha256=kOQjlJFz7Dy6e0NEECoFHhcH8hPIMNeEHxZ1JJ-R2Cs,52372
@@ -447,7 +447,7 @@ letta/services/tool_executor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
447
447
  letta/services/tool_executor/builtin_tool_executor.py,sha256=QKPFXbgzYQobqWRuFs9RhVcQ1d53q8-yV_GFdR2IX88,9718
448
448
  letta/services/tool_executor/composio_tool_executor.py,sha256=ia2AA_WDOseR8Ylam-HEayR7OiyfNSb1sSUrjwqlmFM,2308
449
449
  letta/services/tool_executor/core_tool_executor.py,sha256=bd9x0M9Ttj3FqoIUU66k2FHa2PVY0OlUHqs1aLZYxFI,22624
450
- letta/services/tool_executor/files_tool_executor.py,sha256=HyJ2RXYMVOhvKoLAamgMA2YsgQJmu6CcjioXiaGfAn0,37808
450
+ letta/services/tool_executor/files_tool_executor.py,sha256=nlXk0w-t_dLiN7fpUrnBrHGIwWIEmSkcXa7b_YJs2ME,37781
451
451
  letta/services/tool_executor/mcp_tool_executor.py,sha256=2zEXmkKsH-IGbMG2Xw1S9op4eL21g25RA4vGImB29KY,2000
452
452
  letta/services/tool_executor/multi_agent_tool_executor.py,sha256=LIg9yh8BlfYokP_29WryLZPPSMqEsJUwc0mZGMlhc00,5724
453
453
  letta/services/tool_executor/sandbox_tool_executor.py,sha256=LamEZUVqsiBfBL9S1uQMXFGQOXqJlXK9ZrfpZBk5jsA,6258
@@ -470,8 +470,8 @@ letta/templates/sandbox_code_file_async.py.j2,sha256=lb7nh_P2W9VZHzU_9TxSCEMUod7
470
470
  letta/templates/summary_request_text.j2,sha256=ZttQwXonW2lk4pJLYzLK0pmo4EO4EtUUIXjgXKiizuc,842
471
471
  letta/templates/template_helper.py,sha256=HkG3zwRc5NVGmSTQu5PUTpz7LevK43bzXVaQuN8urf0,1634
472
472
  letta/types/__init__.py,sha256=hokKjCVFGEfR7SLMrtZsRsBfsC7yTIbgKPLdGg4K1eY,147
473
- letta_nightly-0.11.7.dev20250911104039.dist-info/METADATA,sha256=toGJ1-WRYUWzjt9UP7FnP49pm3M_vjCliXaUV3yLsw0,24436
474
- letta_nightly-0.11.7.dev20250911104039.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
475
- letta_nightly-0.11.7.dev20250911104039.dist-info/entry_points.txt,sha256=m-94Paj-kxiR6Ktu0us0_2qfhn29DzF2oVzqBE6cu8w,41
476
- letta_nightly-0.11.7.dev20250911104039.dist-info/licenses/LICENSE,sha256=mExtuZ_GYJgDEI38GWdiEYZizZS4KkVt2SF1g_GPNhI,10759
477
- letta_nightly-0.11.7.dev20250911104039.dist-info/RECORD,,
473
+ letta_nightly-0.11.7.dev20250912104045.dist-info/METADATA,sha256=tqJlpOfovWrr9Go7iI1cwIOkAgFx0Qwf7JYX11vg2JI,24424
474
+ letta_nightly-0.11.7.dev20250912104045.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
475
+ letta_nightly-0.11.7.dev20250912104045.dist-info/entry_points.txt,sha256=m-94Paj-kxiR6Ktu0us0_2qfhn29DzF2oVzqBE6cu8w,41
476
+ letta_nightly-0.11.7.dev20250912104045.dist-info/licenses/LICENSE,sha256=mExtuZ_GYJgDEI38GWdiEYZizZS4KkVt2SF1g_GPNhI,10759
477
+ letta_nightly-0.11.7.dev20250912104045.dist-info/RECORD,,