solace-agent-mesh 1.0.6__py3-none-any.whl → 1.0.7__py3-none-any.whl

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

Potentially problematic release.


This version of solace-agent-mesh might be problematic. Click here for more details.

Files changed (141) hide show
  1. solace_agent_mesh/agent/adk/artifacts/__init__.py +1 -0
  2. solace_agent_mesh/agent/adk/{filesystem_artifact_service.py → artifacts/filesystem_artifact_service.py} +14 -15
  3. solace_agent_mesh/agent/adk/artifacts/s3_artifact_service.py +440 -0
  4. solace_agent_mesh/agent/adk/callbacks.py +123 -159
  5. solace_agent_mesh/agent/adk/embed_resolving_mcp_toolset.py +316 -0
  6. solace_agent_mesh/agent/adk/intelligent_mcp_callbacks.py +414 -0
  7. solace_agent_mesh/agent/adk/mcp_content_processor.py +665 -0
  8. solace_agent_mesh/agent/adk/services.py +35 -1
  9. solace_agent_mesh/agent/adk/setup.py +85 -45
  10. solace_agent_mesh/agent/adk/tool_wrapper.py +19 -3
  11. solace_agent_mesh/agent/protocol/event_handlers.py +1 -1
  12. solace_agent_mesh/agent/sac/app.py +67 -0
  13. solace_agent_mesh/agent/sac/component.py +14 -86
  14. solace_agent_mesh/assets/docs/404.html +3 -3
  15. solace_agent_mesh/assets/docs/assets/js/04989206.b9dfe831.js +1 -0
  16. solace_agent_mesh/assets/docs/assets/js/0e682baa.b3bbde9a.js +1 -0
  17. solace_agent_mesh/assets/docs/assets/js/1023fc19.364235d5.js +1 -0
  18. solace_agent_mesh/assets/docs/assets/js/1523c6b4.1b0ec6f9.js +1 -0
  19. solace_agent_mesh/assets/docs/assets/js/166ab619.e8f3a7c7.js +1 -0
  20. solace_agent_mesh/assets/docs/assets/js/21ceee5f.3bf39250.js +1 -0
  21. solace_agent_mesh/assets/docs/assets/js/3d406171.7d02a73b.js +1 -0
  22. solace_agent_mesh/assets/docs/assets/js/42b3f8d8.8ccb9901.js +1 -0
  23. solace_agent_mesh/assets/docs/assets/js/442a8107.b3159bb2.js +1 -0
  24. solace_agent_mesh/assets/docs/assets/js/4c2787c2.fc6804f2.js +1 -0
  25. solace_agent_mesh/assets/docs/assets/js/5b4258a4.0d080cd9.js +1 -0
  26. solace_agent_mesh/assets/docs/assets/js/75384d09.ccd480c4.js +1 -0
  27. solace_agent_mesh/assets/docs/assets/js/768e31b0.8b51cd70.js +1 -0
  28. solace_agent_mesh/assets/docs/assets/js/945fb41e.c63791d1.js +1 -0
  29. solace_agent_mesh/assets/docs/assets/js/{9eff14a2.036c35ea.js → 9eff14a2.472b0310.js} +1 -1
  30. solace_agent_mesh/assets/docs/assets/js/a3a92b25.4b7fa6a2.js +1 -0
  31. solace_agent_mesh/assets/docs/assets/js/aba87c2f.76376d7c.js +1 -0
  32. solace_agent_mesh/assets/docs/assets/js/ae4415af.7a2f0bbf.js +1 -0
  33. solace_agent_mesh/assets/docs/assets/js/b7006a3a.73a79653.js +1 -0
  34. solace_agent_mesh/assets/docs/assets/js/beecea0d.ae31f6a7.js +1 -0
  35. solace_agent_mesh/assets/docs/assets/js/c2c06897.587b4af5.js +1 -0
  36. solace_agent_mesh/assets/docs/assets/js/{cd3d4052.ca6eed8c.js → cd3d4052.b6535013.js} +1 -1
  37. solace_agent_mesh/assets/docs/assets/js/f284c35a.731836ad.js +1 -0
  38. solace_agent_mesh/assets/docs/assets/js/f897a61a.0aa29dbb.js +1 -0
  39. solace_agent_mesh/assets/docs/assets/js/main.d79f063b.js +2 -0
  40. solace_agent_mesh/assets/docs/assets/js/runtime~main.6415ad00.js +1 -0
  41. solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +28 -4
  42. solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +6 -6
  43. solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +8 -8
  44. solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +5 -5
  45. solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +5 -5
  46. solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +34 -5
  47. solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +4 -4
  48. solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +5 -5
  49. solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +4 -4
  50. solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +6 -6
  51. solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +72 -0
  52. solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +5 -5
  53. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +7 -7
  54. solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +35 -16
  55. solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +4 -4
  56. solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +17 -11
  57. solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +4 -4
  58. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +5 -5
  59. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +4 -4
  60. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +6 -6
  61. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +6 -6
  62. solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +6 -6
  63. solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +4 -4
  64. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +8 -8
  65. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +14 -14
  66. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +8 -8
  67. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +4 -4
  68. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +6 -6
  69. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +35 -23
  70. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +4 -4
  71. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +6 -6
  72. solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +4 -4
  73. solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +4 -4
  74. solace_agent_mesh/assets/docs/lunr-index-1756146501924.json +1 -0
  75. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  76. solace_agent_mesh/assets/docs/search-doc-1756146501924.json +1 -0
  77. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  78. solace_agent_mesh/assets/docs/sitemap.xml +1 -1
  79. solace_agent_mesh/cli/__init__.py +1 -1
  80. solace_agent_mesh/cli/commands/add_cmd/add_cmd_llm.txt +1 -1
  81. solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +67 -10
  82. solace_agent_mesh/cli/commands/add_cmd/gateway_cmd.py +2 -2
  83. solace_agent_mesh/cli/commands/eval_cmd.py +8 -2
  84. solace_agent_mesh/cli/commands/init_cmd/__init__.py +20 -2
  85. solace_agent_mesh/cli/commands/init_cmd/env_step.py +25 -1
  86. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +45 -1
  87. solace_agent_mesh/cli/utils.py +21 -12
  88. solace_agent_mesh/client/webui/frontend/static/assets/main-BucUdn9m.js +673 -0
  89. solace_agent_mesh/client/webui/frontend/static/index.html +1 -1
  90. solace_agent_mesh/common/a2a_protocol.py +1 -1
  91. solace_agent_mesh/common/utils/mime_helpers.py +60 -1
  92. solace_agent_mesh/config_portal/backend/server.py +1 -1
  93. solace_agent_mesh/config_portal/frontend/static/client/assets/{_index-xSu2leR8.js → _index-MqsrTd6g.js} +9 -9
  94. solace_agent_mesh/config_portal/frontend/static/client/assets/{manifest-950eb3be.js → manifest-28271392.js} +1 -1
  95. solace_agent_mesh/config_portal/frontend/static/client/index.html +1 -1
  96. solace_agent_mesh/core_a2a/core_a2a_llm.txt +1 -1
  97. solace_agent_mesh/core_a2a/service.py +1 -1
  98. solace_agent_mesh/evaluation/run.py +149 -15
  99. solace_agent_mesh/evaluation/summary_builder.py +5 -3
  100. solace_agent_mesh/gateway/http_sse/dependencies.py +1 -1
  101. solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +1 -1
  102. solace_agent_mesh/gateway/http_sse/services/task_service.py +1 -1
  103. solace_agent_mesh/llm_detail.txt +2 -2
  104. solace_agent_mesh/templates/agent_template.yaml +1 -1
  105. solace_agent_mesh/templates/plugin_agent_config_template.yaml +3 -3
  106. solace_agent_mesh/templates/plugin_readme_template.md +1 -1
  107. solace_agent_mesh/templates/shared_config.yaml +8 -1
  108. {solace_agent_mesh-1.0.6.dist-info → solace_agent_mesh-1.0.7.dist-info}/METADATA +4 -1
  109. {solace_agent_mesh-1.0.6.dist-info → solace_agent_mesh-1.0.7.dist-info}/RECORD +113 -108
  110. solace_agent_mesh/assets/docs/assets/js/04989206.da8246cd.js +0 -1
  111. solace_agent_mesh/assets/docs/assets/js/0e682baa.79f0ab22.js +0 -1
  112. solace_agent_mesh/assets/docs/assets/js/1023fc19.8e6d174c.js +0 -1
  113. solace_agent_mesh/assets/docs/assets/js/1523c6b4.91c7bc01.js +0 -1
  114. solace_agent_mesh/assets/docs/assets/js/166ab619.7d97ccaf.js +0 -1
  115. solace_agent_mesh/assets/docs/assets/js/21ceee5f.614fa8dd.js +0 -1
  116. solace_agent_mesh/assets/docs/assets/js/3d406171.9b081d5f.js +0 -1
  117. solace_agent_mesh/assets/docs/assets/js/42b3f8d8.36090198.js +0 -1
  118. solace_agent_mesh/assets/docs/assets/js/442a8107.5ba94b65.js +0 -1
  119. solace_agent_mesh/assets/docs/assets/js/4c2787c2.66ee00e9.js +0 -1
  120. solace_agent_mesh/assets/docs/assets/js/5b4258a4.bda20761.js +0 -1
  121. solace_agent_mesh/assets/docs/assets/js/75384d09.c3991823.js +0 -1
  122. solace_agent_mesh/assets/docs/assets/js/768e31b0.a12673db.js +0 -1
  123. solace_agent_mesh/assets/docs/assets/js/945fb41e.74d728aa.js +0 -1
  124. solace_agent_mesh/assets/docs/assets/js/a3a92b25.26ca071f.js +0 -1
  125. solace_agent_mesh/assets/docs/assets/js/aba87c2f.a6b84da6.js +0 -1
  126. solace_agent_mesh/assets/docs/assets/js/ae4415af.96189a93.js +0 -1
  127. solace_agent_mesh/assets/docs/assets/js/b7006a3a.38c0cf3d.js +0 -1
  128. solace_agent_mesh/assets/docs/assets/js/bb2ef573.56931473.js +0 -1
  129. solace_agent_mesh/assets/docs/assets/js/c2c06897.63b76e9e.js +0 -1
  130. solace_agent_mesh/assets/docs/assets/js/f284c35a.5aff74ab.js +0 -1
  131. solace_agent_mesh/assets/docs/assets/js/f897a61a.862b0514.js +0 -1
  132. solace_agent_mesh/assets/docs/assets/js/main.ea9672b6.js +0 -2
  133. solace_agent_mesh/assets/docs/assets/js/runtime~main.aa687c82.js +0 -1
  134. solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +0 -17
  135. solace_agent_mesh/assets/docs/lunr-index-1755285974624.json +0 -1
  136. solace_agent_mesh/assets/docs/search-doc-1755285974624.json +0 -1
  137. solace_agent_mesh/client/webui/frontend/static/assets/main-DzKPMTRs.js +0 -673
  138. /solace_agent_mesh/assets/docs/assets/js/{main.ea9672b6.js.LICENSE.txt → main.d79f063b.js.LICENSE.txt} +0 -0
  139. {solace_agent_mesh-1.0.6.dist-info → solace_agent_mesh-1.0.7.dist-info}/WHEEL +0 -0
  140. {solace_agent_mesh-1.0.6.dist-info → solace_agent_mesh-1.0.7.dist-info}/entry_points.txt +0 -0
  141. {solace_agent_mesh-1.0.6.dist-info → solace_agent_mesh-1.0.7.dist-info}/licenses/LICENSE +0 -0
@@ -22,6 +22,10 @@ from ...common.a2a_protocol import (
22
22
  from google.genai import types as adk_types
23
23
  from google.adk.tools.mcp_tool import MCPTool
24
24
  from solace_ai_connector.common.log import log
25
+ from .intelligent_mcp_callbacks import (
26
+ save_mcp_response_as_artifact_intelligent,
27
+ McpSaveStatus,
28
+ )
25
29
 
26
30
  from ...agent.utils.artifact_helpers import (
27
31
  METADATA_SUFFIX,
@@ -212,6 +216,7 @@ async def process_artifact_blocks_callback(
212
216
  original_func=_internal_create_artifact,
213
217
  tool_config=None, # No specific config for this internal tool
214
218
  tool_name="_internal_create_artifact",
219
+ origin="internal",
215
220
  )
216
221
  save_result = await wrapped_creator(**kwargs_for_call)
217
222
 
@@ -451,102 +456,40 @@ def repair_history_callback(
451
456
  return None
452
457
 
453
458
 
454
- async def _save_mcp_response_as_artifact(
455
- tool: BaseTool,
456
- tool_context: ToolContext,
457
- host_component: "SamAgentComponent",
458
- mcp_response_dict: Dict[str, Any],
459
- original_tool_args: Dict[str, Any],
460
- ) -> Dict[str, Any]:
459
+ def _recursively_clean_pydantic_types(data: Any) -> Any:
461
460
  """
462
- Saves the full MCP tool response as a JSON artifact with associated metadata.
463
-
464
- Args:
465
- tool: The MCPTool instance that generated the response.
466
- tool_context: The ADK ToolContext.
467
- host_component: The A2A_ADK_HostComponent instance for accessing config and services.
468
- mcp_response_dict: The raw MCP tool response dictionary.
469
- original_tool_args: The original arguments passed to the MCP tool.
470
-
471
- Returns:
472
- A dictionary containing details of the saved artifact (filename, version, etc.),
473
- as returned by `save_artifact_with_metadata`.
461
+ Recursively traverses a data structure (dicts, lists) and converts
462
+ Pydantic-specific types like AnyUrl to their primitive string representation
463
+ to ensure JSON serializability.
474
464
  """
475
- log_identifier = f"[CallbackHelper:{tool.name}]"
476
- log.debug("%s Saving MCP response as artifact...", log_identifier)
477
-
478
- try:
479
- a2a_context = tool_context.state.get("a2a_context", {})
480
- logical_task_id = a2a_context.get("logical_task_id", "unknownTask")
481
- task_id_suffix = logical_task_id[-6:]
482
- random_suffix = uuid.uuid4().hex[:6]
483
- filename = f"{task_id_suffix}_{tool.name}_{random_suffix}.json"
484
- log.debug("%s Generated artifact filename: %s", log_identifier, filename)
485
-
486
- content_bytes = json.dumps(mcp_response_dict, indent=2).encode("utf-8")
487
- mime_type = "application/json"
488
- artifact_timestamp = datetime.now(timezone.utc)
489
-
490
- metadata_for_saving = {
491
- "description": f"Full JSON response from MCP tool {tool.name}.",
492
- "source_tool_name": tool.name,
493
- "source_tool_args": original_tool_args,
465
+ if isinstance(data, dict):
466
+ return {
467
+ key: _recursively_clean_pydantic_types(value) for key, value in data.items()
494
468
  }
495
- log.debug("%s Prepared content and metadata for saving.", log_identifier)
496
-
497
- artifact_service = host_component.artifact_service
498
- if not artifact_service:
499
- raise ValueError("ArtifactService is not available on host_component.")
500
-
501
- app_name = host_component.agent_name
502
- user_id = tool_context._invocation_context.user_id
503
- session_id = get_original_session_id(tool_context._invocation_context)
504
- schema_max_keys = host_component.get_config(
505
- "schema_max_keys", DEFAULT_SCHEMA_MAX_KEYS
506
- )
469
+ elif isinstance(data, list):
470
+ return [_recursively_clean_pydantic_types(item) for item in data]
471
+ # Check for Pydantic's AnyUrl without a direct import to avoid dependency issues.
472
+ elif type(data).__name__ == "AnyUrl" and hasattr(data, "__str__"):
473
+ return str(data)
474
+ return data
507
475
 
508
- log.debug(
509
- "%s Calling save_artifact_with_metadata with: app_name=%s, user_id=%s, session_id=%s, filename=%s, schema_max_keys=%d",
510
- log_identifier,
511
- app_name,
512
- user_id,
513
- session_id,
514
- filename,
515
- schema_max_keys,
516
- )
517
476
 
518
- save_result = await save_artifact_with_metadata(
519
- artifact_service=artifact_service,
520
- app_name=app_name,
521
- user_id=user_id,
522
- session_id=session_id,
523
- filename=filename,
524
- content_bytes=content_bytes,
525
- mime_type=mime_type,
526
- metadata_dict=metadata_for_saving,
527
- timestamp=artifact_timestamp,
528
- schema_max_keys=schema_max_keys,
529
- tool_context=tool_context,
530
- )
477
+ def _mcp_response_contains_non_text(mcp_response_dict: Dict[str, Any]) -> bool:
478
+ """
479
+ Checks if the 'content' list in an MCP response dictionary contains any
480
+ items that are not of type 'text'.
481
+ """
482
+ if not isinstance(mcp_response_dict, dict):
483
+ return False
531
484
 
532
- log.info(
533
- "%s MCP response saved as artifact '%s' (version %s). Result: %s",
534
- log_identifier,
535
- save_result.get("data_filename", filename),
536
- save_result.get("data_version", "N/A"),
537
- save_result.get("status"),
538
- )
539
- return save_result
485
+ content_list = mcp_response_dict.get("content")
486
+ if not isinstance(content_list, list):
487
+ return False
540
488
 
541
- except Exception as e:
542
- log.exception(
543
- "%s Error in _save_mcp_response_as_artifact: %s", log_identifier, e
544
- )
545
- return {
546
- "status": "error",
547
- "data_filename": filename if "filename" in locals() else "unknown_filename",
548
- "message": f"Failed to save MCP response as artifact: {e}",
549
- }
489
+ for item in content_list:
490
+ if isinstance(item, dict) and item.get("type") != "text":
491
+ return True
492
+ return False
550
493
 
551
494
 
552
495
  async def manage_large_mcp_tool_responses_callback(
@@ -557,9 +500,24 @@ async def manage_large_mcp_tool_responses_callback(
557
500
  host_component: "SamAgentComponent",
558
501
  ) -> Optional[Dict[str, Any]]:
559
502
  """
560
- Manages large responses from MCP tools by conditionally saving them as artifacts
561
- and/or truncating them before returning to the LLM.
562
- The 'tool_response' is the direct output from the tool's run_async method.
503
+ Manages large or non-textual responses from MCP tools.
504
+
505
+ This callback intercepts the response from an MCPTool. Based on the response's
506
+ size and content type, it performs one or more of the following actions:
507
+ 1. **Saves as Artifact:** If the response size exceeds a configured threshold,
508
+ or if it contains non-textual content (like images), it calls the
509
+ `save_mcp_response_as_artifact_intelligent` function to save the
510
+ response as one or more typed artifacts.
511
+ 2. **Truncates for LLM:** If the response size exceeds a configured limit for
512
+ the LLM, it truncates the content to a preview string.
513
+ 3. **Constructs Final Response:** It builds a new dictionary to be returned
514
+ to the LLM, which includes:
515
+ - A `message_to_llm` summarizing what was done (e.g., saved, truncated).
516
+ - `saved_mcp_response_artifact_details` with the result of the save operation.
517
+ - `mcp_tool_output` containing either the original response or the truncated preview.
518
+ - A `status` field indicating the outcome (e.g., 'processed_and_saved').
519
+
520
+ The `tool_response` is the direct output from the tool's `run_async` method.
563
521
  """
564
522
  log_identifier = f"[Callback:ManageLargeMCPResponse:{tool.name}]"
565
523
  log.info(
@@ -601,6 +559,10 @@ async def manage_large_mcp_tool_responses_callback(
601
559
  )
602
560
  mcp_response_dict = tool_response
603
561
 
562
+ # Clean any Pydantic-specific types before serialization
563
+ mcp_response_dict = _recursively_clean_pydantic_types(mcp_response_dict)
564
+ cleaned_args = _recursively_clean_pydantic_types(args)
565
+
604
566
  try:
605
567
  save_threshold = host_component.get_config(
606
568
  "mcp_tool_response_save_threshold_bytes", 2048
@@ -619,53 +581,43 @@ async def manage_large_mcp_tool_responses_callback(
619
581
  save_threshold = 2048
620
582
  llm_max_bytes = 4096
621
583
 
622
- try:
623
- serialized_original_response_str = json.dumps(mcp_response_dict)
624
- original_response_bytes = len(serialized_original_response_str.encode("utf-8"))
625
- log.debug(
626
- "%s Original response size: %d bytes.",
627
- log_identifier,
628
- original_response_bytes,
629
- )
630
- except TypeError as e:
631
- log.error(
632
- "%s Failed to serialize original MCP tool response dictionary: %s. Returning original response object.",
633
- log_identifier,
634
- e,
635
- )
636
- return tool_response
637
-
638
- needs_truncation_for_llm = original_response_bytes > llm_max_bytes
639
- needs_saving_as_artifact = (
640
- original_response_bytes > save_threshold
641
- ) or needs_truncation_for_llm
642
- log.debug(
643
- "%s Conditions: needs_truncation_for_llm=%s, needs_saving_as_artifact=%s",
644
- log_identifier,
645
- needs_truncation_for_llm,
646
- needs_saving_as_artifact,
647
- )
584
+ contains_non_text_content = _mcp_response_contains_non_text(mcp_response_dict)
585
+ if not contains_non_text_content:
586
+ try:
587
+ serialized_original_response_str = json.dumps(mcp_response_dict)
588
+ original_response_bytes = len(
589
+ serialized_original_response_str.encode("utf-8")
590
+ )
591
+ log.debug(
592
+ "%s Original response size: %d bytes.",
593
+ log_identifier,
594
+ original_response_bytes,
595
+ )
596
+ except TypeError as e:
597
+ log.error(
598
+ "%s Failed to serialize original MCP tool response dictionary: %s. Returning original response object.",
599
+ log_identifier,
600
+ e,
601
+ )
602
+ return tool_response
603
+ needs_truncation_for_llm = original_response_bytes > llm_max_bytes
604
+ needs_saving_as_artifact = (
605
+ original_response_bytes > save_threshold
606
+ ) or needs_truncation_for_llm
607
+ else:
608
+ needs_truncation_for_llm = False
609
+ needs_saving_as_artifact = True
648
610
 
649
- saved_artifact_details = None
611
+ save_result = None
650
612
  if needs_saving_as_artifact:
651
- log.info(
652
- "%s Original response (%d bytes) requires saving (save_threshold=%d, llm_max_bytes=%d). Saving as artifact.",
653
- log_identifier,
654
- original_response_bytes,
655
- save_threshold,
656
- llm_max_bytes,
657
- )
658
- saved_artifact_details = await _save_mcp_response_as_artifact(
659
- tool, tool_context, host_component, mcp_response_dict, args
613
+ save_result = await save_mcp_response_as_artifact_intelligent(
614
+ tool, tool_context, host_component, mcp_response_dict, cleaned_args
660
615
  )
661
- if not (
662
- saved_artifact_details.get("status") == "success"
663
- or saved_artifact_details.get("status") == "partial_success"
664
- ):
616
+ if save_result.status == McpSaveStatus.ERROR:
665
617
  log.warning(
666
618
  "%s Failed to save artifact: %s. Proceeding without saved artifact details.",
667
619
  log_identifier,
668
- saved_artifact_details.get("message"),
620
+ save_result.message,
669
621
  )
670
622
 
671
623
  final_llm_response_dict: Dict[str, Any] = {}
@@ -692,27 +644,36 @@ async def manage_large_mcp_tool_responses_callback(
692
644
  f"The response from tool '{tool.name}' was too large ({original_response_bytes} bytes) for direct display and has been truncated."
693
645
  )
694
646
  log.debug("%s MCP tool output truncated for LLM.", log_identifier)
695
- else:
696
- final_llm_response_dict["mcp_tool_output"] = mcp_response_dict
697
- log.debug(
698
- "%s MCP tool output is the full original response for LLM.", log_identifier
699
- )
700
647
 
701
648
  if needs_saving_as_artifact:
702
- if saved_artifact_details and (
703
- saved_artifact_details.get("status") == "success"
704
- or saved_artifact_details.get("status") == "partial_success"
705
- ):
649
+ if save_result and save_result.status in [
650
+ McpSaveStatus.SUCCESS,
651
+ McpSaveStatus.PARTIAL_SUCCESS,
652
+ ]:
706
653
  final_llm_response_dict["saved_mcp_response_artifact_details"] = (
707
- saved_artifact_details
708
- )
709
- filename = saved_artifact_details.get(
710
- "data_filename", "unknown_artifact.json"
711
- )
712
- version = saved_artifact_details.get("data_version", "N/A")
713
- message_parts_for_llm.append(
714
- f"The full response has been saved as artifact '{filename}' (version {version})."
654
+ save_result.model_dump(exclude_none=True)
715
655
  )
656
+
657
+ total_artifacts = len(save_result.artifacts_saved)
658
+ if total_artifacts > 0:
659
+ first_artifact = save_result.artifacts_saved[0]
660
+ filename = first_artifact.data_filename
661
+ version = first_artifact.data_version
662
+ if total_artifacts > 1:
663
+ message_parts_for_llm.append(
664
+ f"The full response has been saved as {total_artifacts} artifacts, starting with '{filename}' (version {version})."
665
+ )
666
+ else:
667
+ message_parts_for_llm.append(
668
+ f"The full response has been saved as artifact '{filename}' (version {version})."
669
+ )
670
+ elif save_result.fallback_artifact:
671
+ filename = save_result.fallback_artifact.data_filename
672
+ version = save_result.fallback_artifact.data_version
673
+ message_parts_for_llm.append(
674
+ f"The full response has been saved as artifact '{filename}' (version {version})."
675
+ )
676
+
716
677
  log.debug(
717
678
  "%s Added saved artifact details to LLM response.", log_identifier
718
679
  )
@@ -720,21 +681,21 @@ async def manage_large_mcp_tool_responses_callback(
720
681
  message_parts_for_llm.append(
721
682
  "Saving the full response as an artifact failed."
722
683
  )
723
- final_llm_response_dict["saved_mcp_response_artifact_details"] = {
724
- "status": "error",
725
- "message": saved_artifact_details.get(
726
- "message", "Artifact saving failed."
727
- ),
728
- "filename": saved_artifact_details.get("data_filename", "unknown"),
729
- }
684
+ if save_result:
685
+ final_llm_response_dict["saved_mcp_response_artifact_details"] = (
686
+ save_result.model_dump(exclude_none=True)
687
+ )
730
688
  log.warning(
731
689
  "%s Artifact save failed, error details included in LLM response.",
732
690
  log_identifier,
733
691
  )
692
+ else:
693
+ final_llm_response_dict["mcp_tool_output"] = mcp_response_dict
734
694
 
735
695
  if needs_saving_as_artifact and (
736
- saved_artifact_details.get("status") == "success"
737
- or saved_artifact_details.get("status") == "partial_success"
696
+ save_result
697
+ and save_result.status
698
+ in [McpSaveStatus.SUCCESS, McpSaveStatus.PARTIAL_SUCCESS]
738
699
  ):
739
700
  if needs_truncation_for_llm:
740
701
  final_llm_response_dict["status"] = "processed_saved_and_truncated"
@@ -785,6 +746,7 @@ It can span multiple lines.
785
746
 
786
747
  The system will automatically save the content and give you a confirmation in the next turn."""
787
748
 
749
+
788
750
  def _generate_artifact_creation_instruction() -> str:
789
751
  return """
790
752
  **Creating Text-Based Artifacts:**
@@ -806,6 +768,7 @@ def _generate_artifact_creation_instruction() -> str:
806
768
  - Basic explanations that don't require reference material
807
769
  """
808
770
 
771
+
809
772
  def _generate_embed_instruction(
810
773
  include_artifact_content: bool,
811
774
  log_identifier: str,
@@ -1201,6 +1164,7 @@ async def after_tool_callback_inject_metadata(
1201
1164
  metadata_part.inline_data.data.decode("utf-8")
1202
1165
  )
1203
1166
  metadata_dict["version"] = version
1167
+ metadata_dict["filename"] = filename
1204
1168
  formatted_text = format_metadata_for_llm(metadata_dict)
1205
1169
  metadata_texts.append(formatted_text)
1206
1170
  log.info(