language-model-common 2.0.91__tar.gz → 2.0.92__tar.gz

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 (205) hide show
  1. {language_model_common-2.0.91/language_model_common.egg-info → language_model_common-2.0.92}/PKG-INFO +1 -1
  2. language_model_common-2.0.92/VERSION +1 -0
  3. {language_model_common-2.0.91 → language_model_common-2.0.92/language_model_common.egg-info}/PKG-INFO +1 -1
  4. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/converters/streaming_manager.py +9 -0
  5. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/converters/tool_event_handlers.py +31 -8
  6. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/mcp_client/langchain_adapter.py +2 -0
  7. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/mcp_client/tool_invocation.py +80 -11
  8. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/mcp_tool_provider.py +22 -0
  9. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/models/model_factory.py +11 -2
  10. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/structures/openai/request/chat_completion_api_request_wrapper.py +23 -0
  11. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/structures/openai/request/chat_request_wrapper.py +23 -0
  12. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/structures/openai/request/responses_api_request_wrapper.py +36 -2
  13. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/tools/mcp/call_tool_tool.py +11 -4
  14. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/environment/language_model_common_environment_variables.py +29 -0
  15. language_model_common-2.0.91/VERSION +0 -1
  16. {language_model_common-2.0.91 → language_model_common-2.0.92}/LICENSE +0 -0
  17. {language_model_common-2.0.91 → language_model_common-2.0.92}/MANIFEST.in +0 -0
  18. {language_model_common-2.0.91 → language_model_common-2.0.92}/README.md +0 -0
  19. {language_model_common-2.0.91 → language_model_common-2.0.92}/language_model_common.egg-info/SOURCES.txt +0 -0
  20. {language_model_common-2.0.91 → language_model_common-2.0.92}/language_model_common.egg-info/dependency_links.txt +0 -0
  21. {language_model_common-2.0.91 → language_model_common-2.0.92}/language_model_common.egg-info/requires.txt +0 -0
  22. {language_model_common-2.0.91 → language_model_common-2.0.92}/language_model_common.egg-info/top_level.txt +0 -0
  23. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/__init__.py +0 -0
  24. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/auth/__init__.py +0 -0
  25. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/auth/authenticator/__init__.py +0 -0
  26. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/auth/exceptions/__init__.py +0 -0
  27. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/auth/exceptions/authorization_mcp_tool_token_invalid_exception.py +0 -0
  28. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/auth/exceptions/authorization_token_cache_item_expired_exception.py +0 -0
  29. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/auth/exceptions/authorization_token_cache_item_not_found_exception.py +0 -0
  30. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/auth/models/__init__.py +0 -0
  31. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/auth/models/token_cache_item.py +0 -0
  32. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/auth/oauth_provider_registrar.py +0 -0
  33. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/auth/pass_through_token_manager.py +0 -0
  34. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/auth/token_exchange/__init__.py +0 -0
  35. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/auth/token_exchange/token_exchange_manager.py +0 -0
  36. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/auth/token_storage_auth_manager.py +0 -0
  37. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/auth/tools/__init__.py +0 -0
  38. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/auth/tools/tool_auth_manager.py +0 -0
  39. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/aws/__init__.py +0 -0
  40. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/aws/aws_client_factory.py +0 -0
  41. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/configs/__init__.py +0 -0
  42. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/configs/config_reader/__init__.py +0 -0
  43. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/configs/config_reader/config_reader.py +0 -0
  44. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/configs/config_reader/file_config_reader.py +0 -0
  45. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/configs/config_reader/github_directory_downloader.py +0 -0
  46. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/configs/config_reader/github_directory_helper.py +0 -0
  47. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/configs/config_reader/mcp_json_fetcher.py +0 -0
  48. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/configs/config_reader/mcp_json_reader.py +0 -0
  49. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/configs/config_reader/s3_config_reader.py +0 -0
  50. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/configs/prompt_library/__init__.py +0 -0
  51. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/configs/prompt_library/prompt_library_environment_variables.py +0 -0
  52. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/configs/prompt_library/prompt_library_manager.py +0 -0
  53. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/configs/prompt_library/prompt_store.py +0 -0
  54. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/configs/schemas/__init__.py +0 -0
  55. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/configs/schemas/config_schema.py +0 -0
  56. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/configs/schemas/mcp_json_schema.py +0 -0
  57. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/container/__init__.py +0 -0
  58. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/container/container_factory.py +0 -0
  59. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/context/__init__.py +0 -0
  60. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/context/request_context.py +0 -0
  61. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/converters/__init__.py +0 -0
  62. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/converters/langgraph_to_openai_converter.py +0 -0
  63. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/converters/stream_buffer.py +0 -0
  64. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/converters/stream_context_mixin.py +0 -0
  65. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/converters/stream_debug_output_manager.py +0 -0
  66. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/converters/streaming_formatters.py +0 -0
  67. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/converters/streaming_tool_node.py +0 -0
  68. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/exceptions/__init__.py +0 -0
  69. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/exceptions/bailey_exception.py +0 -0
  70. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/exceptions/rate_limit_exception.py +0 -0
  71. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/file_managers/__init__.py +0 -0
  72. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/file_managers/aws_s3_file_manager.py +0 -0
  73. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/file_managers/file_manager.py +0 -0
  74. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/file_managers/file_manager_factory.py +0 -0
  75. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/file_managers/file_writer.py +0 -0
  76. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/file_managers/local_file_manager.py +0 -0
  77. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/github/__init__.py +0 -0
  78. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/github/token_provider.py +0 -0
  79. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/graph/__init__.py +0 -0
  80. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/graph/graph_builder.py +0 -0
  81. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/history/__init__.py +0 -0
  82. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/history/context_compactor.py +0 -0
  83. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/history/conversation_history_manager.py +0 -0
  84. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/history/smart_history_manager.py +0 -0
  85. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/http/__init__.py +0 -0
  86. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/http/http_client_factory.py +0 -0
  87. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/image_generation/__init__.py +0 -0
  88. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/image_generation/aws_image_generator.py +0 -0
  89. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/image_generation/image_generator.py +0 -0
  90. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/image_generation/image_generator_factory.py +0 -0
  91. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/image_generation/managers/__init__.py +0 -0
  92. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/image_generation/managers/image_generation_manager.py +0 -0
  93. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/image_generation/openai_image_generator.py +0 -0
  94. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/image_generation/providers/__init__.py +0 -0
  95. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/image_generation/providers/base_image_generation_provider.py +0 -0
  96. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/image_generation/providers/image_generation_provider.py +0 -0
  97. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/markdown/__init__.py +0 -0
  98. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/markdown/html_to_markdown_converter.py +0 -0
  99. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/__init__.py +0 -0
  100. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/auth/__init__.py +0 -0
  101. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/auth/auth_server_metadata_discovery.py +0 -0
  102. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/auth/mcp_authorization_helper.py +0 -0
  103. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/callbacks.py +0 -0
  104. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/exceptions/__init__.py +0 -0
  105. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/exceptions/mcp_tool_exception.py +0 -0
  106. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/exceptions/mcp_tool_not_found_exception.py +0 -0
  107. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/exceptions/mcp_tool_unauthorized_exception.py +0 -0
  108. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/exceptions/mcp_tool_unknown_exception.py +0 -0
  109. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/interceptors/__init__.py +0 -0
  110. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/interceptors/auth.py +0 -0
  111. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/interceptors/tracing.py +0 -0
  112. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/interceptors/truncation.py +0 -0
  113. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/interceptors/types.py +0 -0
  114. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/mcp_client/__init__.py +0 -0
  115. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/mcp_client/content_conversion.py +0 -0
  116. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/mcp_client/mcp_app_proxy.py +0 -0
  117. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/mcp_client/mcp_tool_list_store.py +0 -0
  118. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/mcp_client/server_card_discovery.py +0 -0
  119. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/mcp_client/session.py +0 -0
  120. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/mcp_client/session_pool.py +0 -0
  121. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/mcp_client/tool_list_cache.py +0 -0
  122. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/mcp_client/ui_resource.py +0 -0
  123. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/tool_catalog.py +0 -0
  124. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mcp/tool_discovery_middleware.py +0 -0
  125. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mocks/__init__.py +0 -0
  126. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mocks/mock_aws_client_factory.py +0 -0
  127. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mocks/mock_http_client_factory.py +0 -0
  128. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mocks/mock_image_generator.py +0 -0
  129. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/mocks/mock_image_generator_factory.py +0 -0
  130. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/models/__init__.py +0 -0
  131. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/models/bedrock_models.py +0 -0
  132. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/models/sanitizing_bedrock_converse.py +0 -0
  133. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/ocr/__init__.py +0 -0
  134. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/ocr/aws_ocr_extractor.py +0 -0
  135. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/ocr/ocr_extractor.py +0 -0
  136. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/ocr/ocr_extractor_factory.py +0 -0
  137. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/persistence/__init__.py +0 -0
  138. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/persistence/persistence_factory.py +0 -0
  139. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/pipeline/__init__.py +0 -0
  140. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/pipeline/context.py +0 -0
  141. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/pipeline/output/__init__.py +0 -0
  142. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/pipeline/pipeline.py +0 -0
  143. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/pipeline/step.py +0 -0
  144. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/pipeline/steps/__init__.py +0 -0
  145. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/py.typed +0 -0
  146. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/schema/__init__.py +0 -0
  147. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/schema/openai/__init__.py +0 -0
  148. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/schema/openai/completions.py +0 -0
  149. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/schema/openai/image_generation.py +0 -0
  150. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/schema/openai/responses.py +0 -0
  151. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/state/__init__.py +0 -0
  152. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/state/messages_state.py +0 -0
  153. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/structures/__init__.py +0 -0
  154. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/structures/openai/__init__.py +0 -0
  155. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/structures/openai/message/__init__.py +0 -0
  156. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/structures/openai/message/chat_completion_api_message_wrapper.py +0 -0
  157. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/structures/openai/message/chat_message_wrapper.py +0 -0
  158. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/structures/openai/message/responses_api_message_wrapper.py +0 -0
  159. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/structures/openai/request/__init__.py +0 -0
  160. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/tools/__init__.py +0 -0
  161. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/tools/mcp/__init__.py +0 -0
  162. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/tools/mcp/search_tools_tool.py +0 -0
  163. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/__init__.py +0 -0
  164. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/cache/__init__.py +0 -0
  165. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/cache/advisory_lock.py +0 -0
  166. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/cache/model_config_cache_store.py +0 -0
  167. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/chat_message_helpers.py +0 -0
  168. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/config_substitution.py +0 -0
  169. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/environment/__init__.py +0 -0
  170. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/header_reader/__init__.py +0 -0
  171. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/header_reader/header_reader.py +0 -0
  172. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/logger/__init__.py +0 -0
  173. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/logger/debug_configuration.py +0 -0
  174. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/logger/exception_formatter.py +0 -0
  175. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/logger/exception_logger.py +0 -0
  176. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/logger/log_levels.py +0 -0
  177. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/logger/logging_response.py +0 -0
  178. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/logger/logging_transport.py +0 -0
  179. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/message_preprocessing/__init__.py +0 -0
  180. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/message_preprocessing/composite_message_preprocessor.py +0 -0
  181. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/message_preprocessing/message_preprocessor.py +0 -0
  182. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/mongo_url_utils.py +0 -0
  183. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/openai/__init__.py +0 -0
  184. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/openai/responses_api_converter.py +0 -0
  185. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/request_information.py +0 -0
  186. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/s3_url.py +0 -0
  187. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/security/__init__.py +0 -0
  188. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/security/normalize.py +0 -0
  189. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/security/off_topic_detector.py +0 -0
  190. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/security/prompt_extraction_detector.py +0 -0
  191. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/security/prompt_sanitizer.py +0 -0
  192. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/slash_command/__init__.py +0 -0
  193. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/slash_command/handlers/__init__.py +0 -0
  194. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/slash_command/handlers/debug_command_handler.py +0 -0
  195. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/slash_command/handlers/skill_command_handler.py +0 -0
  196. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/slash_command/slash_command_handler.py +0 -0
  197. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/slash_command/slash_command_parser.py +0 -0
  198. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/slash_command/slash_command_processor.py +0 -0
  199. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/text_humanizer.py +0 -0
  200. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/token_reducer/__init__.py +0 -0
  201. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/token_reducer/token_reducer.py +0 -0
  202. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/tool_display_name_mapper.py +0 -0
  203. {language_model_common-2.0.91 → language_model_common-2.0.92}/languagemodelcommon/utilities/url_parser.py +0 -0
  204. {language_model_common-2.0.91 → language_model_common-2.0.92}/pyproject.toml +0 -0
  205. {language_model_common-2.0.91 → language_model_common-2.0.92}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: language-model-common
3
- Version: 2.0.91
3
+ Version: 2.0.92
4
4
  Summary: Provides the underlying framework to enhance langchain and add loading configurations
5
5
  Author-email: Imran Qureshi <imran.qureshi@bwell.com>
6
6
  License: Apache-2.0
@@ -0,0 +1 @@
1
+ 2.0.92
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: language-model-common
3
- Version: 2.0.91
3
+ Version: 2.0.92
4
4
  Summary: Provides the underlying framework to enhance langchain and add loading configurations
5
5
  Author-email: Imran Qureshi <imran.qureshi@bwell.com>
6
6
  License: Apache-2.0
@@ -379,6 +379,15 @@ class LangGraphStreamingManager(StreamContextMixin):
379
379
  )
380
380
  if chunk:
381
381
  yield chunk
382
+ elif name == "mcp_tool_heartbeat":
383
+ data = dict(event.get("data", {}))
384
+ chunk = chat_request_wrapper.create_tool_heartbeat_sse_event(
385
+ request_id=request_information.request_id,
386
+ tool_name=data.get("tool_name", "unknown"),
387
+ elapsed_seconds=float(data.get("elapsed_seconds", 0.0)),
388
+ )
389
+ if chunk:
390
+ yield chunk
382
391
  else:
383
392
  logger.debug("Skipped custom event: %s", name)
384
393
 
@@ -36,6 +36,20 @@ from languagemodelcommon.utilities.tool_display_name_mapper import ToolDisplayNa
36
36
  logger = logging.getLogger(__name__)
37
37
  logger.setLevel(SRC_LOG_LEVELS.LLM)
38
38
 
39
+ # Cap on the tool result/error text forwarded to the response.output_item.done
40
+ # SSE event, so a tool that echoes back large content (e.g. a full skill body)
41
+ # doesn't bloat every trace event; full output remains available via the
42
+ # existing debug-file-writer path for cases that need it.
43
+ TOOL_END_OUTPUT_TRACE_MAX_CHARS = 2000
44
+
45
+
46
+ def _truncate_for_trace(
47
+ text: str, *, max_chars: int = TOOL_END_OUTPUT_TRACE_MAX_CHARS
48
+ ) -> str:
49
+ if len(text) <= max_chars:
50
+ return text
51
+ return text[:max_chars] + f"...[truncated {len(text) - max_chars} chars]"
52
+
39
53
 
40
54
  class ToolEventHandler(StreamContextMixin):
41
55
  def __init__(
@@ -161,23 +175,32 @@ class ToolEventHandler(StreamContextMixin):
161
175
  tool_name,
162
176
  )
163
177
 
178
+ tool_message_content: str = (
179
+ convert_message_content_into_string(tool_message=tool_message)
180
+ if tool_message
181
+ else ""
182
+ )
183
+ artifact: Optional[Any] = tool_message.artifact
184
+ # tool_message.status only flips to "error" for an uncaught exception;
185
+ # meta-tools like call_tool catch their own failures and report them via
186
+ # the artifact instead (see CallToolTool), so check both signals.
187
+ is_error: bool = tool_message.status == "error" or (
188
+ isinstance(artifact, dict) and artifact.get("is_error") is True
189
+ )
190
+
164
191
  tool_end_event = chat_request_wrapper.create_tool_end_sse_event(
165
192
  request_id=request_information.request_id,
166
193
  tool_name=tool_name,
167
194
  tool_input=tool_input,
168
195
  runtime_seconds=runtime_seconds,
196
+ output=_truncate_for_trace(tool_message_content)
197
+ if tool_message_content
198
+ else None,
199
+ is_error=is_error,
169
200
  )
170
201
  if tool_end_event:
171
202
  yield tool_end_event
172
203
 
173
- tool_message_content: str = (
174
- convert_message_content_into_string(tool_message=tool_message)
175
- if tool_message
176
- else ""
177
- )
178
-
179
- artifact: Optional[Any] = tool_message.artifact
180
-
181
204
  logger.debug(
182
205
  "Tool %s has artifact of type %s: %s",
183
206
  tool_name,
@@ -62,6 +62,7 @@ def mcp_tool_to_langchain_tool(
62
62
  server_name: str | None = None,
63
63
  session_pool: McpSessionPool | None = None,
64
64
  tool_list_cache: ToolListCache | None = None,
65
+ heartbeat_interval_seconds: float = 15.0,
65
66
  ) -> BaseTool:
66
67
  """Convert an MCP Tool to a LangChain BaseTool.
67
68
 
@@ -84,6 +85,7 @@ def mcp_tool_to_langchain_tool(
84
85
  mcp_callbacks=mcp_callbacks,
85
86
  session_pool=session_pool,
86
87
  tool_list_cache=tool_list_cache,
88
+ heartbeat_interval_seconds=heartbeat_interval_seconds,
87
89
  )
88
90
  handler = build_interceptor_chain(
89
91
  base_handler=execute_tool, tool_interceptors=tool_interceptors
@@ -1,9 +1,12 @@
1
1
  """Tool invocation — interceptor chain and raw MCP tool calls."""
2
2
 
3
+ import asyncio
4
+ import contextlib
3
5
  import logging
4
6
  from collections.abc import Awaitable, Callable
5
7
  from typing import Any
6
8
 
9
+ from langchain_core.callbacks.manager import adispatch_custom_event
7
10
  from mcp.types import CallToolResult
8
11
  from pydantic import ValidationError
9
12
 
@@ -174,8 +177,6 @@ async def _execute_tool_as_task(
174
177
  Raises TaskProtocolError if the server does not respond with a valid
175
178
  CreateTaskResult (e.g., returns a plain CallToolResult instead).
176
179
  """
177
- from langchain_core.callbacks.manager import adispatch_custom_event
178
-
179
180
  try:
180
181
  create_result = await session.experimental.call_tool_as_task(
181
182
  name, arguments, ttl=60000
@@ -222,12 +223,69 @@ async def _execute_tool_as_task(
222
223
  return result
223
224
 
224
225
 
226
+ async def _execute_tool_call_with_heartbeat(
227
+ *,
228
+ session: Any,
229
+ name: str,
230
+ arguments: dict[str, Any],
231
+ progress_callback: Any,
232
+ server_name: str,
233
+ heartbeat_interval_seconds: float,
234
+ ) -> CallToolResult:
235
+ """Call ``session.call_tool``, emitting a synthetic ``mcp_tool_heartbeat``
236
+ custom event every ``heartbeat_interval_seconds`` while the call is in
237
+ flight, regardless of whether the tool reports real progress.
238
+
239
+ Uses ``asyncio.shield`` so a heartbeat tick's wait_for timeout never
240
+ cancels the underlying tool call -- only the local wait is abandoned and
241
+ retried on the same call_task. If this coroutine itself is cancelled
242
+ (e.g. the overall chat turn is aborted), the inner call_task is
243
+ cancelled too rather than left running in the background.
244
+ """
245
+ call_task: "asyncio.Task[CallToolResult]" = asyncio.ensure_future(
246
+ session.call_tool(name, arguments, progress_callback=progress_callback)
247
+ )
248
+ elapsed_seconds = 0.0
249
+ try:
250
+ while True:
251
+ try:
252
+ return await asyncio.wait_for(
253
+ asyncio.shield(call_task), timeout=heartbeat_interval_seconds
254
+ )
255
+ except asyncio.TimeoutError:
256
+ elapsed_seconds += heartbeat_interval_seconds
257
+ try:
258
+ await adispatch_custom_event(
259
+ "mcp_tool_heartbeat",
260
+ {
261
+ "server_name": server_name,
262
+ "tool_name": name,
263
+ "elapsed_seconds": elapsed_seconds,
264
+ },
265
+ )
266
+ except RuntimeError as e:
267
+ logger.debug(
268
+ "Skipping mcp_tool_heartbeat event dispatch: %s (tool=%s)",
269
+ e,
270
+ name,
271
+ )
272
+ finally:
273
+ # Ensure call_task is never left orphaned, whether this coroutine
274
+ # exits via CancelledError, a return, or any other exception raised
275
+ # from the loop body (e.g. adispatch_custom_event).
276
+ if not call_task.done():
277
+ call_task.cancel()
278
+ with contextlib.suppress(asyncio.CancelledError):
279
+ await call_task
280
+
281
+
225
282
  def _make_execute_tool(
226
283
  *,
227
284
  config: MCPConnectionConfig,
228
285
  mcp_callbacks: _MCPCallbacks,
229
286
  session_pool: McpSessionPool | None = None,
230
287
  tool_list_cache: ToolListCache | None = None,
288
+ heartbeat_interval_seconds: float = 15.0,
231
289
  ) -> Callable[[MCPToolCallRequest], Awaitable[MCPToolCallResult]]:
232
290
  """Create an execute_tool handler that opens a session and calls the tool.
233
291
 
@@ -285,10 +343,13 @@ def _make_execute_tool(
285
343
  )
286
344
  if tool_list_cache is not None:
287
345
  await tool_list_cache.invalidate_async(key=cache_key)
288
- return await session.call_tool(
289
- request.name,
290
- request.args,
346
+ return await _execute_tool_call_with_heartbeat(
347
+ session=session,
348
+ name=request.name,
349
+ arguments=request.args,
291
350
  progress_callback=mcp_callbacks.progress_callback,
351
+ server_name=request.server_name,
352
+ heartbeat_interval_seconds=heartbeat_interval_seconds,
292
353
  )
293
354
  except Exception:
294
355
  await session_pool.evict(effective_config)
@@ -324,16 +385,22 @@ def _make_execute_tool(
324
385
  )
325
386
  if tool_list_cache is not None:
326
387
  await tool_list_cache.invalidate_async(key=cache_key)
327
- result = await session.call_tool(
328
- request.name,
329
- request.args,
388
+ result = await _execute_tool_call_with_heartbeat(
389
+ session=session,
390
+ name=request.name,
391
+ arguments=request.args,
330
392
  progress_callback=mcp_callbacks.progress_callback,
393
+ server_name=request.server_name,
394
+ heartbeat_interval_seconds=heartbeat_interval_seconds,
331
395
  )
332
396
  else:
333
- result = await session.call_tool(
334
- request.name,
335
- request.args,
397
+ result = await _execute_tool_call_with_heartbeat(
398
+ session=session,
399
+ name=request.name,
400
+ arguments=request.args,
336
401
  progress_callback=mcp_callbacks.progress_callback,
402
+ server_name=request.server_name,
403
+ heartbeat_interval_seconds=heartbeat_interval_seconds,
337
404
  )
338
405
  except Exception as e:
339
406
  captured_exception = e
@@ -355,6 +422,7 @@ async def call_mcp_tool_raw(
355
422
  tool_interceptors: list[ToolCallInterceptor] | None = None,
356
423
  session_pool: McpSessionPool | None = None,
357
424
  tool_list_cache: ToolListCache | None = None,
425
+ heartbeat_interval_seconds: float = 15.0,
358
426
  ) -> CallToolResult:
359
427
  """Call an MCP tool and return the raw CallToolResult.
360
428
 
@@ -374,6 +442,7 @@ async def call_mcp_tool_raw(
374
442
  mcp_callbacks=mcp_callbacks,
375
443
  session_pool=session_pool,
376
444
  tool_list_cache=tool_list_cache,
445
+ heartbeat_interval_seconds=heartbeat_interval_seconds,
377
446
  )
378
447
  handler = build_interceptor_chain(
379
448
  base_handler=execute_tool, tool_interceptors=tool_interceptors
@@ -6,6 +6,7 @@ from typing import Any, Dict, List
6
6
 
7
7
  import httpx
8
8
  from httpx import HTTPStatusError
9
+ from langchain_core.callbacks.manager import adispatch_custom_event
9
10
  from langchain_core.tools import BaseTool
10
11
  from mcp import ClientSession
11
12
  from mcp.types import (
@@ -210,6 +211,24 @@ class MCPToolProvider:
210
211
  logger.info(
211
212
  f"MCP Tool Progress - Server: {context.server_name}, Progress: {progress}, Total: {total}, Message: {message}"
212
213
  )
214
+ try:
215
+ await adispatch_custom_event(
216
+ "mcp_task_progress",
217
+ {
218
+ "task_id": "",
219
+ "status": f"{progress:g}/{total:g}" if total else f"{progress:g}",
220
+ "message": message,
221
+ "server_name": context.server_name,
222
+ "tool_name": context.tool_name,
223
+ },
224
+ )
225
+ except RuntimeError as e:
226
+ logger.debug(
227
+ "Skipping mcp_task_progress event dispatch: %s (server=%s, tool=%s)",
228
+ e,
229
+ context.server_name,
230
+ context.tool_name,
231
+ )
213
232
 
214
233
  def _build_connection_config(self, tool_config: AgentConfig) -> MCPConnectionConfig:
215
234
  """Build an MCPConnectionConfig from an AgentConfig."""
@@ -285,6 +304,7 @@ class MCPToolProvider:
285
304
  ],
286
305
  server_name=tool_config.name,
287
306
  tool_list_cache=self.tool_list_cache,
307
+ heartbeat_interval_seconds=self.environment_variables.mcp_tool_heartbeat_interval_seconds,
288
308
  )
289
309
  tools.append(langchain_tool)
290
310
 
@@ -451,6 +471,7 @@ class MCPToolProvider:
451
471
  server_name=tool_config.name,
452
472
  session_pool=session_pool,
453
473
  tool_list_cache=self.tool_list_cache,
474
+ heartbeat_interval_seconds=self.environment_variables.mcp_tool_heartbeat_interval_seconds,
454
475
  )
455
476
  for mcp_tool in mcp_tools
456
477
  ]
@@ -1010,6 +1031,7 @@ class MCPToolProvider:
1010
1031
  ],
1011
1032
  session_pool=session_pool,
1012
1033
  tool_list_cache=self.tool_list_cache,
1034
+ heartbeat_interval_seconds=self.environment_variables.mcp_tool_heartbeat_interval_seconds,
1013
1035
  )
1014
1036
 
1015
1037
  async def fetch_mcp_app_embed(
@@ -152,7 +152,7 @@ class ModelFactory:
152
152
  return model_name.startswith(("anthropic.", "us.anthropic."))
153
153
 
154
154
  @staticmethod
155
- def _resolve_anthropic_bedrock_max_tokens(model_name: str) -> int | None:
155
+ def _resolve_anthropic_bedrock_max_tokens(*, model_name: str) -> int | None:
156
156
  """Look up the real max_output_tokens for a Bedrock Anthropic model ID.
157
157
 
158
158
  ``ChatAnthropicBedrock`` inherits ``ChatAnthropic.set_default_max_tokens``,
@@ -174,6 +174,11 @@ class ModelFactory:
174
174
  _get_default_model_profile,
175
175
  )
176
176
  except ImportError:
177
+ logger.warning(
178
+ "langchain_aws._get_default_model_profile unavailable; "
179
+ "falling back to unset max_tokens for %s",
180
+ model_name,
181
+ )
177
182
  return None
178
183
  profile = _get_default_model_profile(model_name)
179
184
  max_output_tokens = profile.get("max_output_tokens")
@@ -192,8 +197,12 @@ class ModelFactory:
192
197
  from pydantic import SecretStr
193
198
  import boto3
194
199
 
200
+ model_parameters_dict = dict(model_parameters_dict)
201
+
195
202
  if "max_tokens" not in model_parameters_dict:
196
- resolved_max_tokens = self._resolve_anthropic_bedrock_max_tokens(model_name)
203
+ resolved_max_tokens = self._resolve_anthropic_bedrock_max_tokens(
204
+ model_name=model_name
205
+ )
197
206
  if resolved_max_tokens is not None:
198
207
  model_parameters_dict["max_tokens"] = resolved_max_tokens
199
208
  logger.info(
@@ -67,6 +67,7 @@ class ChatCompletionApiRequestWrapper(ChatRequestWrapper):
67
67
  enable_debug_logging: bool,
68
68
  environment_variables: "LanguageModelCommonEnvironmentVariables",
69
69
  emit_task_progress: bool | None = None,
70
+ emit_tool_heartbeat: bool | None = None,
70
71
  ) -> None:
71
72
  """
72
73
  Wraps an OpenAI /chat/completions request to provide a consistent interface for different request types.
@@ -84,6 +85,11 @@ class ChatCompletionApiRequestWrapper(ChatRequestWrapper):
84
85
  if emit_task_progress is not None
85
86
  else environment_variables.emit_task_progress_in_chat_completions
86
87
  )
88
+ self._emit_tool_heartbeat: bool = (
89
+ emit_tool_heartbeat
90
+ if emit_tool_heartbeat is not None
91
+ else environment_variables.emit_tool_heartbeat_in_chat_completions
92
+ )
87
93
  self._debug_prefixes = environment_variables.debug_prefixes
88
94
  self._apply_debug_prefix_toggle()
89
95
 
@@ -278,6 +284,23 @@ class ChatCompletionApiRequestWrapper(ChatRequestWrapper):
278
284
  source="task_progress",
279
285
  )
280
286
 
287
+ @override
288
+ def create_tool_heartbeat_sse_event(
289
+ self,
290
+ *,
291
+ request_id: str,
292
+ tool_name: str,
293
+ elapsed_seconds: float,
294
+ ) -> str | None:
295
+ if not self._emit_tool_heartbeat:
296
+ return None
297
+ return self.create_sse_message(
298
+ request_id=request_id,
299
+ content=f"\n[Still running {tool_name}... ({elapsed_seconds:.0f}s)]\n",
300
+ usage_metadata=None,
301
+ source="tool_heartbeat",
302
+ )
303
+
281
304
  @override
282
305
  def create_non_streaming_response(
283
306
  self,
@@ -272,6 +272,21 @@ class ChatRequestWrapper(abc.ABC):
272
272
  """
273
273
  return None
274
274
 
275
+ def create_tool_heartbeat_sse_event(
276
+ self,
277
+ *,
278
+ request_id: str,
279
+ tool_name: str,
280
+ elapsed_seconds: float,
281
+ ) -> str | None:
282
+ """Emit a synthetic heartbeat while a tool call is in flight without
283
+ reporting real progress.
284
+
285
+ The default implementation returns None (no-op). Subclasses override
286
+ to emit the event in their respective SSE formats.
287
+ """
288
+ return None
289
+
275
290
  def create_tool_start_sse_event(
276
291
  self,
277
292
  *,
@@ -294,9 +309,17 @@ class ChatRequestWrapper(abc.ABC):
294
309
  tool_name: str,
295
310
  tool_input: dict[str, Any] | None,
296
311
  runtime_seconds: float | None,
312
+ output: str | None = None,
313
+ is_error: bool = False,
297
314
  ) -> str | None:
298
315
  """Emit an SSE event when a tool finishes execution.
299
316
 
317
+ ``output`` is the tool's own result/error text (already converted to a
318
+ plain string and truncated by the caller) and ``is_error`` reflects the
319
+ underlying ToolMessage's status, so consumers can distinguish a tool
320
+ that ran to completion from one that failed without having to parse
321
+ free-text content.
322
+
300
323
  The default implementation returns None (no-op). Subclasses that
301
324
  support structured tool events (e.g. Responses API) override this
302
325
  to emit a typed SSE frame the client can use for status updates.
@@ -338,8 +338,16 @@ class ResponsesApiRequestWrapper(ChatRequestWrapper):
338
338
  tool_name: str,
339
339
  tool_input: Dict[str, Any] | None,
340
340
  runtime_seconds: float | None,
341
+ output: str | None = None,
342
+ is_error: bool = False,
341
343
  ) -> str | None:
342
- """Emit a ``response.output_item.done`` event with a ``function_call`` item."""
344
+ """Emit a ``response.output_item.done`` event with a ``function_call`` item.
345
+
346
+ Includes ``runtime_seconds`` and the tool's own ``output``/``is_error``
347
+ so consuming UIs (e.g. baileyai-skills-service's Stream events panel)
348
+ can show how long a call took and surface failures, rather than only
349
+ ever seeing a "completed" status with no result.
350
+ """
343
351
  event: Dict[str, Any] = {
344
352
  "type": "response.output_item.done",
345
353
  "output_index": 0,
@@ -350,7 +358,10 @@ class ResponsesApiRequestWrapper(ChatRequestWrapper):
350
358
  "call_id": f"call_{request_id}_{tool_name}",
351
359
  "name": tool_name,
352
360
  "arguments": json.dumps(tool_input) if tool_input else "",
353
- "status": "completed",
361
+ "status": "failed" if is_error else "completed",
362
+ "runtime_seconds": runtime_seconds,
363
+ "output": output or "",
364
+ "is_error": is_error,
354
365
  },
355
366
  }
356
367
  return f"data: {json.dumps(event)}\n\n"
@@ -373,6 +384,29 @@ class ResponsesApiRequestWrapper(ChatRequestWrapper):
373
384
  }
374
385
  return f"data: {json.dumps(event)}\n\n"
375
386
 
387
+ @override
388
+ def create_tool_heartbeat_sse_event(
389
+ self,
390
+ *,
391
+ request_id: str,
392
+ tool_name: str,
393
+ elapsed_seconds: float,
394
+ ) -> str | None:
395
+ """Emit a synthetic heartbeat as a ``task.progress`` SSE event.
396
+
397
+ Reuses the exact same wire format as create_task_progress_sse_event
398
+ so existing consumers (e.g. baileyai-skills-service's frontend, which
399
+ already treats any `type: "task.progress"` payload as a non-content
400
+ trace event) require no changes.
401
+ """
402
+ event: Dict[str, Any] = {
403
+ "type": "task.progress",
404
+ "task_id": "",
405
+ "status": "in_progress",
406
+ "message": f"Still running {tool_name}... ({elapsed_seconds:.0f}s)",
407
+ }
408
+ return f"data: {json.dumps(event)}\n\n"
409
+
376
410
  @staticmethod
377
411
  def _convert_usage_to_response_usage(
378
412
  usages: list[UsageMetadata],
@@ -98,7 +98,7 @@ class CallToolTool(BaseTool):
98
98
  if entry is None:
99
99
  return (
100
100
  f"Tool '{name}' not found. Use search_tools to find available tools.",
101
- None,
101
+ {"is_error": True},
102
102
  )
103
103
 
104
104
  try:
@@ -122,9 +122,16 @@ class CallToolTool(BaseTool):
122
122
  session_token=self.session_token,
123
123
  )
124
124
 
125
+ # Surface the *inner* tool's own error status (result.isError), not just
126
+ # transport-level failures — CallToolTool itself never raises for a
127
+ # rejected/invalid call, so this is the only place that signal exists.
125
128
  artifact: dict[str, Any] | None = None
126
- if app_embed is not None:
127
- artifact = {"mcp_app_embed": app_embed}
129
+ if app_embed is not None or result.isError:
130
+ artifact = {}
131
+ if app_embed is not None:
132
+ artifact["mcp_app_embed"] = app_embed
133
+ if result.isError:
134
+ artifact["is_error"] = True
128
135
 
129
136
  return text, artifact
130
137
  except AuthorizationNeededException:
@@ -138,4 +145,4 @@ class CallToolTool(BaseTool):
138
145
  entry.server_name,
139
146
  error_detail,
140
147
  )
141
- return f"Error calling tool '{name}': {error_detail}", None
148
+ return f"Error calling tool '{name}': {error_detail}", {"is_error": True}
@@ -17,6 +17,7 @@ logger = logging.getLogger(__name__)
17
17
  DEFAULT_STREAMING_BUFFER_FLUSH_INTERVAL_SECONDS = 0.05
18
18
  DEFAULT_LANGGRAPH_MAX_CONCURRENCY = 4
19
19
  DEFAULT_LANGGRAPH_RECURSION_LIMIT = 100
20
+ DEFAULT_MCP_TOOL_HEARTBEAT_INTERVAL_SECONDS = 15.0
20
21
 
21
22
  # Default generic error message when not exposing technical details
22
23
  DEFAULT_GENERIC_ERROR_MESSAGE = (
@@ -289,6 +290,34 @@ class LanguageModelCommonEnvironmentVariables(
289
290
  """Timeout in seconds for tool calls."""
290
291
  return int(os.environ.get("TOOL_CALL_TIMEOUT_SECONDS", "600"))
291
292
 
293
+ @property
294
+ def mcp_tool_heartbeat_interval_seconds(self) -> float:
295
+ """Interval in seconds between synthetic heartbeat events emitted
296
+ while an MCP tool call is in flight without reporting progress."""
297
+ value = os.environ.get("MCP_TOOL_HEARTBEAT_INTERVAL_SECONDS")
298
+ if value is None:
299
+ return DEFAULT_MCP_TOOL_HEARTBEAT_INTERVAL_SECONDS
300
+ try:
301
+ parsed = float(value)
302
+ return max(1.0, parsed)
303
+ except ValueError:
304
+ logger.warning(
305
+ "Invalid MCP_TOOL_HEARTBEAT_INTERVAL_SECONDS value '%s'; using default=%s",
306
+ value,
307
+ DEFAULT_MCP_TOOL_HEARTBEAT_INTERVAL_SECONDS,
308
+ )
309
+ return DEFAULT_MCP_TOOL_HEARTBEAT_INTERVAL_SECONDS
310
+
311
+ @property
312
+ def emit_tool_heartbeat_in_chat_completions(self) -> bool:
313
+ """When True, synthetic MCP tool heartbeats are emitted as content
314
+ deltas in the Chat Completions streaming format. Separate from
315
+ emit_task_progress_in_chat_completions so enabling one does not
316
+ change the volume/behavior of the other."""
317
+ return self.str2bool(
318
+ os.environ.get("EMIT_TOOL_HEARTBEAT_IN_CHAT_COMPLETIONS", "false")
319
+ )
320
+
292
321
  @property
293
322
  def app_login_uri(self) -> str:
294
323
  value = os.environ.get("APP_LOGIN_URI")
@@ -1 +0,0 @@
1
- 2.0.91