letta-nightly 0.11.7.dev20251007104119__py3-none-any.whl → 0.12.0.dev20251009104148__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. letta/__init__.py +1 -1
  2. letta/adapters/letta_llm_adapter.py +1 -0
  3. letta/adapters/letta_llm_request_adapter.py +0 -1
  4. letta/adapters/letta_llm_stream_adapter.py +7 -2
  5. letta/adapters/simple_llm_request_adapter.py +88 -0
  6. letta/adapters/simple_llm_stream_adapter.py +192 -0
  7. letta/agents/agent_loop.py +6 -0
  8. letta/agents/ephemeral_summary_agent.py +2 -1
  9. letta/agents/helpers.py +142 -6
  10. letta/agents/letta_agent.py +13 -33
  11. letta/agents/letta_agent_batch.py +2 -4
  12. letta/agents/letta_agent_v2.py +87 -77
  13. letta/agents/letta_agent_v3.py +927 -0
  14. letta/agents/voice_agent.py +2 -6
  15. letta/constants.py +8 -4
  16. letta/database_utils.py +161 -0
  17. letta/errors.py +40 -0
  18. letta/functions/function_sets/base.py +84 -4
  19. letta/functions/function_sets/multi_agent.py +0 -3
  20. letta/functions/schema_generator.py +113 -71
  21. letta/groups/dynamic_multi_agent.py +3 -2
  22. letta/groups/helpers.py +1 -2
  23. letta/groups/round_robin_multi_agent.py +3 -2
  24. letta/groups/sleeptime_multi_agent.py +3 -2
  25. letta/groups/sleeptime_multi_agent_v2.py +1 -1
  26. letta/groups/sleeptime_multi_agent_v3.py +17 -17
  27. letta/groups/supervisor_multi_agent.py +84 -80
  28. letta/helpers/converters.py +3 -0
  29. letta/helpers/message_helper.py +4 -0
  30. letta/helpers/tool_rule_solver.py +92 -5
  31. letta/interfaces/anthropic_streaming_interface.py +409 -0
  32. letta/interfaces/gemini_streaming_interface.py +296 -0
  33. letta/interfaces/openai_streaming_interface.py +752 -1
  34. letta/llm_api/anthropic_client.py +127 -16
  35. letta/llm_api/bedrock_client.py +4 -2
  36. letta/llm_api/deepseek_client.py +4 -1
  37. letta/llm_api/google_vertex_client.py +124 -42
  38. letta/llm_api/groq_client.py +4 -1
  39. letta/llm_api/llm_api_tools.py +11 -4
  40. letta/llm_api/llm_client_base.py +6 -2
  41. letta/llm_api/openai.py +32 -2
  42. letta/llm_api/openai_client.py +423 -18
  43. letta/llm_api/xai_client.py +4 -1
  44. letta/main.py +9 -5
  45. letta/memory.py +1 -0
  46. letta/orm/__init__.py +2 -1
  47. letta/orm/agent.py +10 -0
  48. letta/orm/block.py +7 -16
  49. letta/orm/blocks_agents.py +8 -2
  50. letta/orm/files_agents.py +2 -0
  51. letta/orm/job.py +7 -5
  52. letta/orm/mcp_oauth.py +1 -0
  53. letta/orm/message.py +21 -6
  54. letta/orm/organization.py +2 -0
  55. letta/orm/provider.py +6 -2
  56. letta/orm/run.py +71 -0
  57. letta/orm/run_metrics.py +82 -0
  58. letta/orm/sandbox_config.py +7 -1
  59. letta/orm/sqlalchemy_base.py +0 -306
  60. letta/orm/step.py +6 -5
  61. letta/orm/step_metrics.py +5 -5
  62. letta/otel/tracing.py +28 -3
  63. letta/plugins/defaults.py +4 -4
  64. letta/prompts/system_prompts/__init__.py +2 -0
  65. letta/prompts/system_prompts/letta_v1.py +25 -0
  66. letta/schemas/agent.py +3 -2
  67. letta/schemas/agent_file.py +9 -3
  68. letta/schemas/block.py +23 -10
  69. letta/schemas/enums.py +21 -2
  70. letta/schemas/job.py +17 -4
  71. letta/schemas/letta_message_content.py +71 -2
  72. letta/schemas/letta_stop_reason.py +5 -5
  73. letta/schemas/llm_config.py +53 -3
  74. letta/schemas/memory.py +1 -1
  75. letta/schemas/message.py +564 -117
  76. letta/schemas/openai/responses_request.py +64 -0
  77. letta/schemas/providers/__init__.py +2 -0
  78. letta/schemas/providers/anthropic.py +16 -0
  79. letta/schemas/providers/ollama.py +115 -33
  80. letta/schemas/providers/openrouter.py +52 -0
  81. letta/schemas/providers/vllm.py +2 -1
  82. letta/schemas/run.py +48 -42
  83. letta/schemas/run_metrics.py +21 -0
  84. letta/schemas/step.py +2 -2
  85. letta/schemas/step_metrics.py +1 -1
  86. letta/schemas/tool.py +15 -107
  87. letta/schemas/tool_rule.py +88 -5
  88. letta/serialize_schemas/marshmallow_agent.py +1 -0
  89. letta/server/db.py +79 -408
  90. letta/server/rest_api/app.py +61 -10
  91. letta/server/rest_api/dependencies.py +14 -0
  92. letta/server/rest_api/redis_stream_manager.py +19 -8
  93. letta/server/rest_api/routers/v1/agents.py +364 -292
  94. letta/server/rest_api/routers/v1/blocks.py +14 -20
  95. letta/server/rest_api/routers/v1/identities.py +45 -110
  96. letta/server/rest_api/routers/v1/internal_templates.py +21 -0
  97. letta/server/rest_api/routers/v1/jobs.py +23 -6
  98. letta/server/rest_api/routers/v1/messages.py +1 -1
  99. letta/server/rest_api/routers/v1/runs.py +149 -99
  100. letta/server/rest_api/routers/v1/sandbox_configs.py +10 -19
  101. letta/server/rest_api/routers/v1/tools.py +281 -594
  102. letta/server/rest_api/routers/v1/voice.py +1 -1
  103. letta/server/rest_api/streaming_response.py +29 -29
  104. letta/server/rest_api/utils.py +122 -64
  105. letta/server/server.py +160 -887
  106. letta/services/agent_manager.py +236 -919
  107. letta/services/agent_serialization_manager.py +16 -0
  108. letta/services/archive_manager.py +0 -100
  109. letta/services/block_manager.py +211 -168
  110. letta/services/context_window_calculator/token_counter.py +1 -1
  111. letta/services/file_manager.py +1 -1
  112. letta/services/files_agents_manager.py +24 -33
  113. letta/services/group_manager.py +0 -142
  114. letta/services/helpers/agent_manager_helper.py +7 -2
  115. letta/services/helpers/run_manager_helper.py +69 -0
  116. letta/services/job_manager.py +96 -411
  117. letta/services/lettuce/__init__.py +6 -0
  118. letta/services/lettuce/lettuce_client_base.py +86 -0
  119. letta/services/mcp_manager.py +38 -6
  120. letta/services/message_manager.py +165 -362
  121. letta/services/organization_manager.py +0 -36
  122. letta/services/passage_manager.py +0 -345
  123. letta/services/provider_manager.py +0 -80
  124. letta/services/run_manager.py +364 -0
  125. letta/services/sandbox_config_manager.py +0 -234
  126. letta/services/step_manager.py +62 -39
  127. letta/services/summarizer/summarizer.py +9 -7
  128. letta/services/telemetry_manager.py +0 -16
  129. letta/services/tool_executor/builtin_tool_executor.py +35 -0
  130. letta/services/tool_executor/core_tool_executor.py +397 -2
  131. letta/services/tool_executor/files_tool_executor.py +3 -3
  132. letta/services/tool_executor/multi_agent_tool_executor.py +30 -15
  133. letta/services/tool_executor/tool_execution_manager.py +6 -8
  134. letta/services/tool_executor/tool_executor_base.py +3 -3
  135. letta/services/tool_manager.py +85 -339
  136. letta/services/tool_sandbox/base.py +24 -13
  137. letta/services/tool_sandbox/e2b_sandbox.py +16 -1
  138. letta/services/tool_schema_generator.py +123 -0
  139. letta/services/user_manager.py +0 -99
  140. letta/settings.py +20 -4
  141. letta/system.py +5 -1
  142. {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.12.0.dev20251009104148.dist-info}/METADATA +3 -5
  143. {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.12.0.dev20251009104148.dist-info}/RECORD +146 -135
  144. letta/agents/temporal/activities/__init__.py +0 -4
  145. letta/agents/temporal/activities/example_activity.py +0 -7
  146. letta/agents/temporal/activities/prepare_messages.py +0 -10
  147. letta/agents/temporal/temporal_agent_workflow.py +0 -56
  148. letta/agents/temporal/types.py +0 -25
  149. {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.12.0.dev20251009104148.dist-info}/WHEEL +0 -0
  150. {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.12.0.dev20251009104148.dist-info}/entry_points.txt +0 -0
  151. {letta_nightly-0.11.7.dev20251007104119.dist-info → letta_nightly-0.12.0.dev20251009104148.dist-info}/licenses/LICENSE +0 -0
@@ -23,6 +23,7 @@ from anthropic.types.beta import (
23
23
  BetaThinkingDelta,
24
24
  BetaToolUseBlock,
25
25
  )
26
+ from letta_client.types import assistant_message
26
27
 
27
28
  from letta.constants import DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG
28
29
  from letta.local_llm.constants import INNER_THOUGHTS_KWARG
@@ -65,9 +66,13 @@ class AnthropicStreamingInterface:
65
66
  use_assistant_message: bool = False,
66
67
  put_inner_thoughts_in_kwarg: bool = False,
67
68
  requires_approval_tools: list = [],
69
+ run_id: str | None = None,
70
+ step_id: str | None = None,
68
71
  ):
69
72
  self.json_parser: JSONParser = PydanticJSONParser()
70
73
  self.use_assistant_message = use_assistant_message
74
+ self.run_id = run_id
75
+ self.step_id = step_id
71
76
 
72
77
  # Premake IDs for database writes
73
78
  self.letta_message_id = Message.generate_id()
@@ -271,11 +276,15 @@ class AnthropicStreamingInterface:
271
276
  if not self.use_assistant_message:
272
277
  # Only buffer the initial tool call message if it doesn't require approval
273
278
  # For approval-required tools, we'll create the ApprovalRequestMessage later
279
+ if prev_message_type and prev_message_type != "tool_call_message":
280
+ message_index += 1
274
281
  if self.tool_call_name not in self.requires_approval_tools:
275
282
  tool_call_msg = ToolCallMessage(
276
283
  id=self.letta_message_id,
277
284
  tool_call=ToolCallDelta(name=self.tool_call_name, tool_call_id=self.tool_call_id),
278
285
  date=datetime.now(timezone.utc).isoformat(),
286
+ otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
287
+ run_id=self.run_id,
279
288
  )
280
289
  self.tool_call_buffer.append(tool_call_msg)
281
290
  elif isinstance(content, BetaThinkingBlock):
@@ -291,6 +300,8 @@ class AnthropicStreamingInterface:
291
300
  hidden_reasoning=content.data,
292
301
  date=datetime.now(timezone.utc).isoformat(),
293
302
  otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
303
+ run_id=self.run_id,
304
+ step_id=self.step_id,
294
305
  )
295
306
  self.reasoning_messages.append(hidden_reasoning_message)
296
307
  prev_message_type = hidden_reasoning_message.message_type
@@ -336,6 +347,8 @@ class AnthropicStreamingInterface:
336
347
  reasoning=self.accumulated_inner_thoughts[-1],
337
348
  date=datetime.now(timezone.utc).isoformat(),
338
349
  otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
350
+ run_id=self.run_id,
351
+ step_id=self.step_id,
339
352
  )
340
353
  self.reasoning_messages.append(reasoning_message)
341
354
  prev_message_type = reasoning_message.message_type
@@ -363,6 +376,8 @@ class AnthropicStreamingInterface:
363
376
  reasoning=inner_thoughts_diff,
364
377
  date=datetime.now(timezone.utc).isoformat(),
365
378
  otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
379
+ run_id=self.run_id,
380
+ step_id=self.step_id,
366
381
  )
367
382
  self.reasoning_messages.append(reasoning_message)
368
383
  prev_message_type = reasoning_message.message_type
@@ -393,6 +408,7 @@ class AnthropicStreamingInterface:
393
408
  tool_call_id=self.tool_call_id,
394
409
  arguments=tool_call_args,
395
410
  ),
411
+ run_id=self.run_id,
396
412
  )
397
413
  prev_message_type = approval_msg.message_type
398
414
  yield approval_msg
@@ -416,6 +432,7 @@ class AnthropicStreamingInterface:
416
432
  tool_call_id=self.tool_call_id,
417
433
  arguments=tool_call_args,
418
434
  ),
435
+ run_id=self.run_id,
419
436
  )
420
437
  prev_message_type = tool_call_msg.message_type
421
438
  yield tool_call_msg
@@ -436,6 +453,7 @@ class AnthropicStreamingInterface:
436
453
  content=[TextContent(text=send_message_diff)],
437
454
  date=datetime.now(timezone.utc).isoformat(),
438
455
  otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
456
+ run_id=self.run_id,
439
457
  )
440
458
  prev_message_type = assistant_msg.message_type
441
459
  yield assistant_msg
@@ -446,12 +464,14 @@ class AnthropicStreamingInterface:
446
464
  id=self.letta_message_id,
447
465
  tool_call=ToolCallDelta(name=self.tool_call_name, tool_call_id=self.tool_call_id, arguments=delta.partial_json),
448
466
  date=datetime.now(timezone.utc).isoformat(),
467
+ run_id=self.run_id,
449
468
  )
450
469
  else:
451
470
  tool_call_msg = ToolCallMessage(
452
471
  id=self.letta_message_id,
453
472
  tool_call=ToolCallDelta(name=self.tool_call_name, tool_call_id=self.tool_call_id, arguments=delta.partial_json),
454
473
  date=datetime.now(timezone.utc).isoformat(),
474
+ run_id=self.run_id,
455
475
  )
456
476
  if self.inner_thoughts_complete:
457
477
  if prev_message_type and prev_message_type != "tool_call_message":
@@ -479,6 +499,8 @@ class AnthropicStreamingInterface:
479
499
  reasoning=delta.thinking,
480
500
  date=datetime.now(timezone.utc).isoformat(),
481
501
  otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
502
+ run_id=self.run_id,
503
+ step_id=self.step_id,
482
504
  )
483
505
  self.reasoning_messages.append(reasoning_message)
484
506
  prev_message_type = reasoning_message.message_type
@@ -499,6 +521,8 @@ class AnthropicStreamingInterface:
499
521
  date=datetime.now(timezone.utc).isoformat(),
500
522
  signature=delta.signature,
501
523
  otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
524
+ run_id=self.run_id,
525
+ step_id=self.step_id,
502
526
  )
503
527
  self.reasoning_messages.append(reasoning_message)
504
528
  prev_message_type = reasoning_message.message_type
@@ -522,3 +546,388 @@ class AnthropicStreamingInterface:
522
546
  self.tool_call_buffer = []
523
547
 
524
548
  self.anthropic_mode = None
549
+
550
+
551
+ class SimpleAnthropicStreamingInterface:
552
+ """
553
+ A simpler version of AnthropicStreamingInterface that doesn't handle send_message parsing on inner_thoughts_in_kwargs
554
+ """
555
+
556
+ def __init__(
557
+ self,
558
+ requires_approval_tools: list = [],
559
+ run_id: str | None = None,
560
+ step_id: str | None = None,
561
+ ):
562
+ self.json_parser: JSONParser = PydanticJSONParser()
563
+ self.run_id = run_id
564
+ self.step_id = step_id
565
+
566
+ # Premake IDs for database writes
567
+ self.letta_message_id = Message.generate_id()
568
+
569
+ self.anthropic_mode = None
570
+ self.message_id = None
571
+ self.accumulated_inner_thoughts = []
572
+ self.tool_call_id = None
573
+ self.tool_call_name = None
574
+ self.accumulated_tool_call_args = ""
575
+ self.previous_parse = {}
576
+
577
+ # usage trackers
578
+ self.input_tokens = 0
579
+ self.output_tokens = 0
580
+ self.model = None
581
+
582
+ # reasoning object trackers
583
+ self.reasoning_messages = []
584
+
585
+ # assistant object trackers
586
+ self.assistant_messages: list[AssistantMessage] = []
587
+
588
+ # Buffer to hold tool call messages until inner thoughts are complete
589
+ self.tool_call_buffer = []
590
+ self.inner_thoughts_complete = False
591
+
592
+ # Buffer to handle partial XML tags across chunks
593
+ self.partial_tag_buffer = ""
594
+
595
+ self.requires_approval_tools = requires_approval_tools
596
+
597
+ def get_tool_call_object(self) -> Optional[ToolCall]:
598
+ """Useful for agent loop"""
599
+ if not self.tool_call_name:
600
+ return None
601
+
602
+ # hack for tool rules
603
+ try:
604
+ tool_input = json.loads(self.accumulated_tool_call_args)
605
+ except json.JSONDecodeError as e:
606
+ # Attempt to use OptimisticJSONParser to handle incomplete/malformed JSON
607
+ try:
608
+ tool_input = self.json_parser.parse(self.accumulated_tool_call_args)
609
+ except:
610
+ logger.warning(
611
+ f"Failed to decode tool call arguments for tool_call_id={self.tool_call_id}, "
612
+ f"name={self.tool_call_name}. Raw input: {self.accumulated_tool_call_args!r}. Error: {e}"
613
+ )
614
+ raise e
615
+ if "id" in tool_input and tool_input["id"].startswith("toolu_") and "function" in tool_input:
616
+ arguments = str(json.dumps(tool_input["function"]["arguments"], indent=2))
617
+ else:
618
+ arguments = str(json.dumps(tool_input, indent=2))
619
+ return ToolCall(id=self.tool_call_id, function=FunctionCall(arguments=arguments, name=self.tool_call_name))
620
+
621
+ def get_reasoning_content(self) -> list[TextContent | ReasoningContent | RedactedReasoningContent]:
622
+ def _process_group(
623
+ group: list[ReasoningMessage | HiddenReasoningMessage | AssistantMessage],
624
+ group_type: str,
625
+ ) -> TextContent | ReasoningContent | RedactedReasoningContent:
626
+ if group_type == "reasoning":
627
+ reasoning_text = "".join(chunk.reasoning for chunk in group).strip()
628
+ is_native = any(chunk.source == "reasoner_model" for chunk in group)
629
+ signature = next((chunk.signature for chunk in group if chunk.signature is not None), None)
630
+ if is_native:
631
+ return ReasoningContent(is_native=is_native, reasoning=reasoning_text, signature=signature)
632
+ else:
633
+ return TextContent(text=reasoning_text)
634
+ elif group_type == "redacted":
635
+ redacted_text = "".join(chunk.hidden_reasoning for chunk in group if chunk.hidden_reasoning is not None)
636
+ return RedactedReasoningContent(data=redacted_text)
637
+ elif group_type == "text":
638
+ concat = ""
639
+ for chunk in group:
640
+ if isinstance(chunk.content, list):
641
+ concat += "".join([c.text for c in chunk.content])
642
+ else:
643
+ concat += chunk.content
644
+ return TextContent(text=concat)
645
+ else:
646
+ raise ValueError("Unexpected group type")
647
+
648
+ merged = []
649
+ current_group = []
650
+ current_group_type = None # "reasoning" or "redacted"
651
+
652
+ for msg in self.reasoning_messages:
653
+ # Determine the type of the current message
654
+ if isinstance(msg, HiddenReasoningMessage):
655
+ msg_type = "redacted"
656
+ elif isinstance(msg, ReasoningMessage):
657
+ msg_type = "reasoning"
658
+ elif isinstance(msg, AssistantMessage):
659
+ msg_type = "text"
660
+ else:
661
+ raise ValueError("Unexpected message type")
662
+
663
+ # Initialize group type if not set
664
+ if current_group_type is None:
665
+ current_group_type = msg_type
666
+
667
+ # If the type changes, process the current group
668
+ if msg_type != current_group_type:
669
+ merged.append(_process_group(current_group, current_group_type))
670
+ current_group = []
671
+ current_group_type = msg_type
672
+
673
+ current_group.append(msg)
674
+
675
+ # Process the final group, if any.
676
+ if current_group:
677
+ merged.append(_process_group(current_group, current_group_type))
678
+
679
+ return merged
680
+
681
+ def get_content(self) -> list[TextContent | ReasoningContent | RedactedReasoningContent]:
682
+ return self.get_reasoning_content()
683
+ # concat = ""
684
+ # for msg in self.assistant_messages:
685
+ # if isinstance(msg.content, list):
686
+ # concat += "".join([c.text for c in msg.content])
687
+ # else:
688
+ # concat += msg.content
689
+ # return [TextContent(text=concat)]
690
+
691
+ async def process(
692
+ self,
693
+ stream: AsyncStream[BetaRawMessageStreamEvent],
694
+ ttft_span: Optional["Span"] = None,
695
+ ) -> AsyncGenerator[LettaMessage | LettaStopReason, None]:
696
+ prev_message_type = None
697
+ message_index = 0
698
+ event = None
699
+ try:
700
+ async with stream:
701
+ async for event in stream:
702
+ try:
703
+ async for message in self._process_event(event, ttft_span, prev_message_type, message_index):
704
+ new_message_type = message.message_type
705
+ if new_message_type != prev_message_type:
706
+ if prev_message_type != None:
707
+ message_index += 1
708
+ prev_message_type = new_message_type
709
+ # print(f"Yielding message: {message}")
710
+ yield message
711
+ except asyncio.CancelledError as e:
712
+ import traceback
713
+
714
+ logger.info("Cancelled stream attempt but overriding %s: %s", e, traceback.format_exc())
715
+ async for message in self._process_event(event, ttft_span, prev_message_type, message_index):
716
+ new_message_type = message.message_type
717
+ if new_message_type != prev_message_type:
718
+ if prev_message_type != None:
719
+ message_index += 1
720
+ prev_message_type = new_message_type
721
+ yield message
722
+
723
+ # Don't raise the exception here
724
+ continue
725
+
726
+ except Exception as e:
727
+ import traceback
728
+
729
+ logger.error("Error processing stream: %s\n%s", e, traceback.format_exc())
730
+ if ttft_span:
731
+ ttft_span.add_event(
732
+ name="stop_reason",
733
+ attributes={"stop_reason": StopReasonType.error.value, "error": str(e), "stacktrace": traceback.format_exc()},
734
+ )
735
+ yield LettaStopReason(stop_reason=StopReasonType.error)
736
+ raise e
737
+ finally:
738
+ logger.info("AnthropicStreamingInterface: Stream processing complete.")
739
+
740
+ async def _process_event(
741
+ self,
742
+ event: BetaRawMessageStreamEvent,
743
+ ttft_span: Optional["Span"] = None,
744
+ prev_message_type: Optional[str] = None,
745
+ message_index: int = 0,
746
+ ) -> AsyncGenerator[LettaMessage | LettaStopReason, None]:
747
+ """Process a single event from the Anthropic stream and yield any resulting messages.
748
+
749
+ Args:
750
+ event: The event to process
751
+
752
+ Yields:
753
+ Messages generated from processing this event
754
+ """
755
+ if isinstance(event, BetaRawContentBlockStartEvent):
756
+ content = event.content_block
757
+
758
+ if isinstance(content, BetaTextBlock):
759
+ self.anthropic_mode = EventMode.TEXT
760
+ # TODO: Can capture citations, etc.
761
+
762
+ elif isinstance(content, BetaToolUseBlock):
763
+ self.anthropic_mode = EventMode.TOOL_USE
764
+ self.tool_call_id = content.id
765
+ self.tool_call_name = content.name
766
+
767
+ if self.tool_call_name in self.requires_approval_tools:
768
+ if prev_message_type and prev_message_type != "approval_request_message":
769
+ message_index += 1
770
+ tool_call_msg = ApprovalRequestMessage(
771
+ id=self.letta_message_id,
772
+ tool_call=ToolCallDelta(name=self.tool_call_name, tool_call_id=self.tool_call_id),
773
+ date=datetime.now(timezone.utc).isoformat(),
774
+ otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
775
+ run_id=self.run_id,
776
+ step_id=self.step_id,
777
+ )
778
+ else:
779
+ if prev_message_type and prev_message_type != "tool_call_message":
780
+ message_index += 1
781
+ tool_call_msg = ToolCallMessage(
782
+ id=self.letta_message_id,
783
+ tool_call=ToolCallDelta(name=self.tool_call_name, tool_call_id=self.tool_call_id),
784
+ date=datetime.now(timezone.utc).isoformat(),
785
+ otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
786
+ run_id=self.run_id,
787
+ step_id=self.step_id,
788
+ )
789
+ prev_message_type = tool_call_msg.message_type
790
+ yield tool_call_msg
791
+
792
+ elif isinstance(content, BetaThinkingBlock):
793
+ self.anthropic_mode = EventMode.THINKING
794
+ # TODO: Can capture signature, etc.
795
+
796
+ elif isinstance(content, BetaRedactedThinkingBlock):
797
+ self.anthropic_mode = EventMode.REDACTED_THINKING
798
+
799
+ if prev_message_type and prev_message_type != "hidden_reasoning_message":
800
+ message_index += 1
801
+
802
+ hidden_reasoning_message = HiddenReasoningMessage(
803
+ id=self.letta_message_id,
804
+ state="redacted",
805
+ hidden_reasoning=content.data,
806
+ date=datetime.now(timezone.utc).isoformat(),
807
+ otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
808
+ run_id=self.run_id,
809
+ step_id=self.step_id,
810
+ )
811
+
812
+ self.reasoning_messages.append(hidden_reasoning_message)
813
+ prev_message_type = hidden_reasoning_message.message_type
814
+ yield hidden_reasoning_message
815
+
816
+ elif isinstance(event, BetaRawContentBlockDeltaEvent):
817
+ delta = event.delta
818
+
819
+ if isinstance(delta, BetaTextDelta):
820
+ # Safety check
821
+ if not self.anthropic_mode == EventMode.TEXT:
822
+ raise RuntimeError(f"Streaming integrity failed - received BetaTextDelta object while not in TEXT EventMode: {delta}")
823
+
824
+ if prev_message_type and prev_message_type != "assistant_message":
825
+ message_index += 1
826
+
827
+ assistant_msg = AssistantMessage(
828
+ id=self.letta_message_id,
829
+ # content=[TextContent(text=delta.text)],
830
+ content=delta.text,
831
+ date=datetime.now(timezone.utc).isoformat(),
832
+ otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
833
+ run_id=self.run_id,
834
+ step_id=self.step_id,
835
+ )
836
+ # self.assistant_messages.append(assistant_msg)
837
+ self.reasoning_messages.append(assistant_msg)
838
+ prev_message_type = assistant_msg.message_type
839
+ yield assistant_msg
840
+
841
+ elif isinstance(delta, BetaInputJSONDelta):
842
+ if not self.anthropic_mode == EventMode.TOOL_USE:
843
+ raise RuntimeError(
844
+ f"Streaming integrity failed - received BetaInputJSONDelta object while not in TOOL_USE EventMode: {delta}"
845
+ )
846
+
847
+ self.accumulated_tool_call_args += delta.partial_json
848
+
849
+ if self.tool_call_name in self.requires_approval_tools:
850
+ if prev_message_type and prev_message_type != "approval_request_message":
851
+ message_index += 1
852
+ tool_call_msg = ApprovalRequestMessage(
853
+ id=self.letta_message_id,
854
+ tool_call=ToolCallDelta(name=self.tool_call_name, tool_call_id=self.tool_call_id, arguments=delta.partial_json),
855
+ date=datetime.now(timezone.utc).isoformat(),
856
+ otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
857
+ run_id=self.run_id,
858
+ step_id=self.step_id,
859
+ )
860
+ else:
861
+ if prev_message_type and prev_message_type != "tool_call_message":
862
+ message_index += 1
863
+ tool_call_msg = ToolCallMessage(
864
+ id=self.letta_message_id,
865
+ tool_call=ToolCallDelta(name=self.tool_call_name, tool_call_id=self.tool_call_id, arguments=delta.partial_json),
866
+ date=datetime.now(timezone.utc).isoformat(),
867
+ otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
868
+ run_id=self.run_id,
869
+ step_id=self.step_id,
870
+ )
871
+
872
+ yield tool_call_msg
873
+
874
+ elif isinstance(delta, BetaThinkingDelta):
875
+ # Safety check
876
+ if not self.anthropic_mode == EventMode.THINKING:
877
+ raise RuntimeError(
878
+ f"Streaming integrity failed - received BetaThinkingBlock object while not in THINKING EventMode: {delta}"
879
+ )
880
+
881
+ if prev_message_type and prev_message_type != "reasoning_message":
882
+ message_index += 1
883
+ reasoning_message = ReasoningMessage(
884
+ id=self.letta_message_id,
885
+ source="reasoner_model",
886
+ reasoning=delta.thinking,
887
+ date=datetime.now(timezone.utc).isoformat(),
888
+ otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
889
+ run_id=self.run_id,
890
+ step_id=self.step_id,
891
+ )
892
+ self.reasoning_messages.append(reasoning_message)
893
+ prev_message_type = reasoning_message.message_type
894
+ yield reasoning_message
895
+
896
+ elif isinstance(delta, BetaSignatureDelta):
897
+ # Safety check
898
+ if not self.anthropic_mode == EventMode.THINKING:
899
+ raise RuntimeError(
900
+ f"Streaming integrity failed - received BetaSignatureDelta object while not in THINKING EventMode: {delta}"
901
+ )
902
+
903
+ if prev_message_type and prev_message_type != "reasoning_message":
904
+ message_index += 1
905
+ reasoning_message = ReasoningMessage(
906
+ id=self.letta_message_id,
907
+ source="reasoner_model",
908
+ reasoning="",
909
+ date=datetime.now(timezone.utc).isoformat(),
910
+ signature=delta.signature,
911
+ otid=Message.generate_otid_from_id(self.letta_message_id, message_index),
912
+ run_id=self.run_id,
913
+ step_id=self.step_id,
914
+ )
915
+ self.reasoning_messages.append(reasoning_message)
916
+ prev_message_type = reasoning_message.message_type
917
+ yield reasoning_message
918
+
919
+ elif isinstance(event, BetaRawMessageStartEvent):
920
+ self.message_id = event.message.id
921
+ self.input_tokens += event.message.usage.input_tokens
922
+ self.output_tokens += event.message.usage.output_tokens
923
+ self.model = event.message.model
924
+
925
+ elif isinstance(event, BetaRawMessageDeltaEvent):
926
+ self.output_tokens += event.usage.output_tokens
927
+
928
+ elif isinstance(event, BetaRawMessageStopEvent):
929
+ # Don't do anything here! We don't want to stop the stream.
930
+ pass
931
+
932
+ elif isinstance(event, BetaRawContentBlockStopEvent):
933
+ self.anthropic_mode = None