solace-agent-mesh 1.0.8__py3-none-any.whl → 1.1.0__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.
- solace_agent_mesh/agent/adk/adk_llm.txt +182 -42
- solace_agent_mesh/agent/adk/artifacts/artifacts_llm.txt +171 -0
- solace_agent_mesh/agent/adk/callbacks.py +165 -104
- solace_agent_mesh/agent/adk/embed_resolving_mcp_toolset.py +0 -18
- solace_agent_mesh/agent/adk/models/models_llm.txt +104 -55
- solace_agent_mesh/agent/adk/runner.py +7 -5
- solace_agent_mesh/agent/adk/setup.py +11 -0
- solace_agent_mesh/agent/adk/stream_parser.py +8 -1
- solace_agent_mesh/agent/adk/tool_wrapper.py +10 -3
- solace_agent_mesh/agent/agent_llm.txt +355 -18
- solace_agent_mesh/agent/protocol/event_handlers.py +433 -296
- solace_agent_mesh/agent/protocol/protocol_llm.txt +54 -7
- solace_agent_mesh/agent/sac/app.py +1 -1
- solace_agent_mesh/agent/sac/component.py +212 -517
- solace_agent_mesh/agent/sac/sac_llm.txt +133 -63
- solace_agent_mesh/agent/testing/testing_llm.txt +25 -58
- solace_agent_mesh/agent/tools/peer_agent_tool.py +15 -11
- solace_agent_mesh/agent/tools/tools_llm.txt +234 -69
- solace_agent_mesh/agent/utils/artifact_helpers.py +35 -1
- solace_agent_mesh/agent/utils/utils_llm.txt +90 -105
- solace_agent_mesh/assets/docs/404.html +3 -3
- solace_agent_mesh/assets/docs/assets/js/{3d406171.7d02a73b.js → 3d406171.0b9eeed1.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/6e0db977.39a79ca9.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{75384d09.ccd480c4.js → 75384d09.bf78fbdb.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/90dd9cf6.88f385ea.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/f284c35a.fb68323a.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/main.a75ecc0d.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/runtime~main.458efb1d.js +1 -0
- solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/migration-guides/a2a-upgrade-to-0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html +105 -0
- solace_agent_mesh/assets/docs/docs/documentation/migration-guides/a2a-upgrade-to-0.3.0/a2a-technical-migration-map/index.html +53 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +8 -8
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +4 -4
- solace_agent_mesh/assets/docs/lunr-index-1756992446316.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -1
- solace_agent_mesh/assets/docs/search-doc-1756992446316.json +1 -0
- solace_agent_mesh/assets/docs/search-doc.json +1 -1
- solace_agent_mesh/assets/docs/sitemap.xml +1 -1
- solace_agent_mesh/cli/__init__.py +1 -1
- solace_agent_mesh/cli/commands/add_cmd/web_add_agent_step.py +12 -3
- solace_agent_mesh/cli/commands/add_cmd/web_add_gateway_step.py +10 -14
- solace_agent_mesh/cli/commands/init_cmd/web_init_step.py +2 -15
- solace_agent_mesh/cli/commands/plugin_cmd/catalog_cmd.py +6 -2
- solace_agent_mesh/cli/utils.py +15 -0
- solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-DvlO62me.js → authCallback-BmF2l6vg.js} +1 -1
- solace_agent_mesh/client/webui/frontend/static/assets/{client-bp6u3qVZ.js → client-D881Dttc.js} +4 -4
- solace_agent_mesh/client/webui/frontend/static/assets/main-C0jZjYa8.js +699 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-CCeG324-.css +1 -0
- solace_agent_mesh/client/webui/frontend/static/auth-callback.html +2 -2
- solace_agent_mesh/client/webui/frontend/static/index.html +3 -3
- solace_agent_mesh/common/a2a/__init__.py +213 -0
- solace_agent_mesh/common/a2a/a2a_llm.txt +182 -0
- solace_agent_mesh/common/a2a/artifact.py +328 -0
- solace_agent_mesh/common/a2a/events.py +183 -0
- solace_agent_mesh/common/a2a/message.py +307 -0
- solace_agent_mesh/common/a2a/protocol.py +513 -0
- solace_agent_mesh/common/a2a/task.py +127 -0
- solace_agent_mesh/common/a2a/translation.py +653 -0
- solace_agent_mesh/common/a2a/types.py +54 -0
- solace_agent_mesh/common/a2a_spec/a2a.json +2576 -0
- solace_agent_mesh/common/a2a_spec/a2a_spec_llm.txt +407 -0
- solace_agent_mesh/common/a2a_spec/schemas/agent_progress_update.json +18 -0
- solace_agent_mesh/common/a2a_spec/schemas/artifact_creation_progress.json +31 -0
- solace_agent_mesh/common/a2a_spec/schemas/llm_invocation.json +18 -0
- solace_agent_mesh/common/a2a_spec/schemas/schemas_llm.txt +235 -0
- solace_agent_mesh/common/a2a_spec/schemas/tool_invocation_start.json +26 -0
- solace_agent_mesh/common/a2a_spec/schemas/tool_result.json +25 -0
- solace_agent_mesh/common/agent_registry.py +1 -1
- solace_agent_mesh/common/common_llm.txt +192 -70
- solace_agent_mesh/common/data_parts.py +99 -0
- solace_agent_mesh/common/middleware/middleware_llm.txt +17 -17
- solace_agent_mesh/common/sac/__init__.py +0 -0
- solace_agent_mesh/common/sac/sac_llm.txt +71 -0
- solace_agent_mesh/common/sac/sam_component_base.py +252 -0
- solace_agent_mesh/common/services/providers/providers_llm.txt +51 -84
- solace_agent_mesh/common/services/services_llm.txt +206 -26
- solace_agent_mesh/common/utils/artifact_utils.py +29 -0
- solace_agent_mesh/common/utils/embeds/embeds_llm.txt +176 -80
- solace_agent_mesh/common/utils/utils_llm.txt +323 -42
- solace_agent_mesh/config_portal/backend/common.py +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/{_index-MqsrTd6g.js → _index-Bym6YkMd.js} +74 -24
- solace_agent_mesh/config_portal/frontend/static/client/assets/{components-B7lKcHVY.js → components-Rk0n-9cK.js} +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/{entry.client-CEumGClk.js → entry.client-mvZjNKiz.js} +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/{index-DSo1AH_7.js → index-DzNKzXrc.js} +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-d845808d.js +1 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/{root-C4XmHinv.js → root-BWvk5-gF.js} +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/index.html +3 -3
- solace_agent_mesh/core_a2a/core_a2a_llm.txt +10 -8
- solace_agent_mesh/core_a2a/service.py +20 -44
- solace_agent_mesh/gateway/base/app.py +27 -1
- solace_agent_mesh/gateway/base/base_llm.txt +177 -72
- solace_agent_mesh/gateway/base/component.py +294 -523
- solace_agent_mesh/gateway/gateway_llm.txt +299 -58
- solace_agent_mesh/gateway/http_sse/component.py +156 -183
- solace_agent_mesh/gateway/http_sse/components/components_llm.txt +29 -29
- solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +272 -36
- solace_agent_mesh/gateway/http_sse/main.py +8 -10
- solace_agent_mesh/gateway/http_sse/routers/agents.py +1 -1
- solace_agent_mesh/gateway/http_sse/routers/artifacts.py +18 -4
- solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +231 -5
- solace_agent_mesh/gateway/http_sse/routers/sessions.py +12 -7
- solace_agent_mesh/gateway/http_sse/routers/tasks.py +116 -169
- solace_agent_mesh/gateway/http_sse/services/agent_service.py +1 -1
- solace_agent_mesh/gateway/http_sse/services/services_llm.txt +89 -135
- solace_agent_mesh/gateway/http_sse/services/task_service.py +2 -5
- solace_agent_mesh/solace_agent_mesh_llm.txt +362 -0
- solace_agent_mesh/templates/gateway_component_template.py +149 -98
- {solace_agent_mesh-1.0.8.dist-info → solace_agent_mesh-1.1.0.dist-info}/METADATA +5 -4
- {solace_agent_mesh-1.0.8.dist-info → solace_agent_mesh-1.1.0.dist-info}/RECORD +143 -126
- solace_agent_mesh/assets/docs/assets/js/f284c35a.731836ad.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/main.6dba4a66.js +0 -2
- solace_agent_mesh/assets/docs/assets/js/runtime~main.6415ad00.js +0 -1
- solace_agent_mesh/assets/docs/lunr-index-1756153049706.json +0 -1
- solace_agent_mesh/assets/docs/search-doc-1756153049706.json +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-BCpII1-0.css +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-BucUdn9m.js +0 -673
- solace_agent_mesh/common/a2a_protocol.py +0 -564
- solace_agent_mesh/common/client/__init__.py +0 -4
- solace_agent_mesh/common/client/card_resolver.py +0 -21
- solace_agent_mesh/common/client/client.py +0 -85
- solace_agent_mesh/common/client/client_llm.txt +0 -133
- solace_agent_mesh/common/server/__init__.py +0 -4
- solace_agent_mesh/common/server/server.py +0 -122
- solace_agent_mesh/common/server/server_llm.txt +0 -169
- solace_agent_mesh/common/server/task_manager.py +0 -291
- solace_agent_mesh/common/server/utils.py +0 -28
- solace_agent_mesh/common/types.py +0 -411
- solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-28271392.js +0 -1
- /solace_agent_mesh/assets/docs/assets/js/{main.6dba4a66.js.LICENSE.txt → main.a75ecc0d.js.LICENSE.txt} +0 -0
- {solace_agent_mesh-1.0.8.dist-info → solace_agent_mesh-1.1.0.dist-info}/WHEEL +0 -0
- {solace_agent_mesh-1.0.8.dist-info → solace_agent_mesh-1.1.0.dist-info}/entry_points.txt +0 -0
- {solace_agent_mesh-1.0.8.dist-info → solace_agent_mesh-1.1.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# DEVELOPER GUIDE: a2a
|
|
2
|
+
|
|
3
|
+
## Quick Summary
|
|
4
|
+
The `a2a` directory provides a comprehensive abstraction layer for the A2A (Agent-to-Agent) protocol, offering helper functions for creating, consuming, and translating A2A protocol objects. It acts as a facade that insulates applications from the specifics of the underlying a2a-sdk, providing simplified interfaces for messages, artifacts, tasks, events, and protocol-level operations.
|
|
5
|
+
|
|
6
|
+
## Files Overview
|
|
7
|
+
- `__init__.py` - Main entry point exposing all commonly used A2A helpers
|
|
8
|
+
- `artifact.py` - Helpers for creating and consuming A2A Artifact objects
|
|
9
|
+
- `events.py` - Helpers for creating and consuming A2A asynchronous event objects
|
|
10
|
+
- `message.py` - Helpers for creating and consuming A2A Message and Part objects
|
|
11
|
+
- `protocol.py` - Helpers for A2A protocol-level concerns like topic construction and JSON-RPC
|
|
12
|
+
- `task.py` - Helpers for creating and consuming A2A Task objects
|
|
13
|
+
- `translation.py` - Helpers for translating between A2A protocol objects and other domains
|
|
14
|
+
- `types.py` - Custom type aliases and models for the A2A helper layer
|
|
15
|
+
|
|
16
|
+
## Developer API Reference
|
|
17
|
+
|
|
18
|
+
### __init__.py
|
|
19
|
+
**Purpose:** Main entry point that exposes all commonly used A2A helpers for easy access
|
|
20
|
+
**Import:** `from solace_agent_mesh.common.a2a import *`
|
|
21
|
+
|
|
22
|
+
This file re-exports all public functions from the other modules, allowing developers to import everything from the main package.
|
|
23
|
+
|
|
24
|
+
### artifact.py
|
|
25
|
+
**Purpose:** Provides helpers for creating and consuming A2A Artifact objects
|
|
26
|
+
**Import:** `from solace_agent_mesh.common.a2a.artifact import create_text_artifact, create_data_artifact, get_artifact_id`
|
|
27
|
+
|
|
28
|
+
**Functions:**
|
|
29
|
+
- `create_text_artifact(name: str, text: str, description: str = "", artifact_id: Optional[str] = None) -> Artifact` - Creates a new Artifact containing a single TextPart
|
|
30
|
+
- `create_data_artifact(name: str, data: dict[str, Any], description: str = "", artifact_id: Optional[str] = None) -> Artifact` - Creates a new Artifact containing a single DataPart
|
|
31
|
+
- `update_artifact_parts(artifact: Artifact, new_parts: List[ContentPart]) -> Artifact` - Returns a new Artifact with replaced parts
|
|
32
|
+
- `prepare_file_part_for_publishing(part: FilePart, mode: str, artifact_service: "BaseArtifactService", user_id: str, session_id: str, target_agent_name: str, log_identifier: str) -> Optional[FilePart]` - Prepares a FilePart for publishing based on the artifact handling mode
|
|
33
|
+
- `resolve_file_part_uri(part: FilePart, artifact_service: "BaseArtifactService", log_identifier: str) -> FilePart` - Resolves an artifact URI within a FilePart into embedded bytes
|
|
34
|
+
- `get_artifact_id(artifact: Artifact) -> str` - Safely retrieves the ID from an Artifact
|
|
35
|
+
- `get_artifact_name(artifact: Artifact) -> Optional[str]` - Safely retrieves the name from an Artifact
|
|
36
|
+
- `get_parts_from_artifact(artifact: Artifact) -> List[ContentPart]` - Extracts unwrapped content parts from an Artifact
|
|
37
|
+
|
|
38
|
+
**Usage Examples:**
|
|
39
|
+
```python
|
|
40
|
+
from solace_agent_mesh.common.a2a.artifact import create_text_artifact, get_artifact_id
|
|
41
|
+
|
|
42
|
+
# Create a text artifact
|
|
43
|
+
artifact = create_text_artifact(
|
|
44
|
+
name="My Document",
|
|
45
|
+
text="This is the content of my document",
|
|
46
|
+
description="A sample text document"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Get artifact ID
|
|
50
|
+
artifact_id = get_artifact_id(artifact)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### events.py
|
|
54
|
+
**Purpose:** Provides helpers for creating and consuming A2A asynchronous event objects
|
|
55
|
+
**Import:** `from solace_agent_mesh.common.a2a.events import create_status_update, create_artifact_update`
|
|
56
|
+
|
|
57
|
+
**Functions:**
|
|
58
|
+
- `create_data_signal_event(task_id: str, context_id: str, signal_data: SignalData, agent_name: str, part_metadata: Optional[Dict[str, Any]] = None) -> TaskStatusUpdateEvent` - Creates a TaskStatusUpdateEvent from signal data
|
|
59
|
+
- `create_status_update(task_id: str, context_id: str, message: Message, is_final: bool = False, metadata: Optional[Dict[str, Any]] = None) -> TaskStatusUpdateEvent` - Creates a new TaskStatusUpdateEvent
|
|
60
|
+
- `create_artifact_update(task_id: str, context_id: str, artifact: Artifact, append: bool = False, last_chunk: bool = False, metadata: Optional[Dict[str, Any]] = None) -> TaskArtifactUpdateEvent` - Creates a new TaskArtifactUpdateEvent
|
|
61
|
+
- `get_message_from_status_update(event: TaskStatusUpdateEvent) -> Optional[Message]` - Extracts Message from TaskStatusUpdateEvent
|
|
62
|
+
- `get_data_parts_from_status_update(event: TaskStatusUpdateEvent) -> List[DataPart]` - Extracts DataPart objects from status update
|
|
63
|
+
- `get_artifact_from_artifact_update(event: TaskArtifactUpdateEvent) -> Optional[Artifact]` - Extracts Artifact from TaskArtifactUpdateEvent
|
|
64
|
+
|
|
65
|
+
**Usage Examples:**
|
|
66
|
+
```python
|
|
67
|
+
from solace_agent_mesh.common.a2a.events import create_status_update
|
|
68
|
+
from solace_agent_mesh.common.a2a.message import create_agent_text_message
|
|
69
|
+
|
|
70
|
+
# Create a status update event
|
|
71
|
+
message = create_agent_text_message("Processing your request...")
|
|
72
|
+
status_event = create_status_update(
|
|
73
|
+
task_id="task-123",
|
|
74
|
+
context_id="context-456",
|
|
75
|
+
message=message,
|
|
76
|
+
is_final=False
|
|
77
|
+
)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### message.py
|
|
81
|
+
**Purpose:** Provides helpers for creating and consuming A2A Message and Part objects
|
|
82
|
+
**Import:** `from solace_agent_mesh.common.a2a.message import create_agent_text_message, create_text_part, get_text_from_message`
|
|
83
|
+
|
|
84
|
+
**Functions:**
|
|
85
|
+
- `create_agent_text_message(text: str, task_id: Optional[str] = None, context_id: Optional[str] = None, message_id: Optional[str] = None) -> Message` - Creates agent message with TextPart
|
|
86
|
+
- `create_agent_data_message(data: dict[str, Any], task_id: Optional[str] = None, context_id: Optional[str] = None, message_id: Optional[str] = None, part_metadata: Optional[Dict[str, Any]] = None) -> Message` - Creates agent message with DataPart
|
|
87
|
+
- `create_agent_parts_message(parts: List[ContentPart], task_id: Optional[str] = None, context_id: Optional[str] = None, message_id: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None) -> Message` - Creates agent message with multiple parts
|
|
88
|
+
- `create_user_message(parts: List[ContentPart], task_id: Optional[str] = None, context_id: Optional[str] = None, message_id: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None) -> Message` - Creates user message with multiple parts
|
|
89
|
+
- `create_text_part(text: str, metadata: Optional[Dict[str, Any]] = None) -> TextPart` - Creates a TextPart object
|
|
90
|
+
- `create_file_part_from_uri(uri: str, name: Optional[str] = None, mime_type: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None) -> FilePart` - Creates FilePart from URI
|
|
91
|
+
- `create_file_part_from_bytes(content_bytes: bytes, name: Optional[str] = None, mime_type: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None) -> FilePart` - Creates FilePart from bytes
|
|
92
|
+
- `create_data_part(data: Dict[str, Any], metadata: Optional[Dict[str, Any]] = None) -> DataPart` - Creates a DataPart object
|
|
93
|
+
- `update_message_parts(message: Message, new_parts: List[ContentPart]) -> Message` - Returns a new Message with replaced parts
|
|
94
|
+
- `get_text_from_message(message: Message, delimiter: str = "\n") -> str` - Extracts and joins all text content from Message
|
|
95
|
+
- `get_data_parts_from_message(message: Message) -> List[DataPart]` - Extracts DataPart objects from Message
|
|
96
|
+
- `get_file_parts_from_message(message: Message) -> List[FilePart]` - Extracts FilePart objects from Message
|
|
97
|
+
- `get_message_id(message: Message) -> str` - Gets message ID
|
|
98
|
+
- `get_context_id(message: Message) -> Optional[str]` - Gets context ID
|
|
99
|
+
- `get_task_id(message: Message) -> Optional[str]` - Gets task ID
|
|
100
|
+
- `get_parts_from_message(message: Message) -> List[ContentPart]` - Extracts unwrapped content parts from Message
|
|
101
|
+
- `get_text_from_text_part(part: TextPart) -> str` - Gets text from TextPart
|
|
102
|
+
- `get_data_from_data_part(part: DataPart) -> Dict[str, Any]` - Gets data from DataPart
|
|
103
|
+
- `get_metadata_from_part(part: ContentPart) -> Optional[Dict[str, Any]]` - Gets metadata from any Part
|
|
104
|
+
- `get_file_from_file_part(part: FilePart) -> Optional[Union[FileWithUri, FileWithBytes]]` - Gets File object from FilePart
|
|
105
|
+
- `get_uri_from_file_part(part: FilePart) -> Optional[str]` - Gets URI from FilePart
|
|
106
|
+
- `get_bytes_from_file_part(part: FilePart) -> Optional[bytes]` - Gets decoded bytes from FilePart
|
|
107
|
+
- `get_filename_from_file_part(part: FilePart) -> Optional[str]` - Gets filename from FilePart
|
|
108
|
+
- `get_mimetype_from_file_part(part: FilePart) -> Optional[str]` - Gets MIME type from FilePart
|
|
109
|
+
|
|
110
|
+
**Usage Examples:**
|
|
111
|
+
```python
|
|
112
|
+
from solace_agent_mesh.common.a2a.message import create_agent_text_message, create_text_part, create_user_message
|
|
113
|
+
|
|
114
|
+
# Create a simple text message
|
|
115
|
+
message = create_agent_text_message(
|
|
116
|
+
text="Hello, how can I help you?",
|
|
117
|
+
task_id="task-123",
|
|
118
|
+
context_id="context-456"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Create a user message with multiple parts
|
|
122
|
+
text_part = create_text_part("Please analyze this data:")
|
|
123
|
+
data_part = create_data_part({"values": [1, 2, 3, 4, 5]})
|
|
124
|
+
user_message = create_user_message(
|
|
125
|
+
parts=[text_part, data_part],
|
|
126
|
+
task_id="task-123"
|
|
127
|
+
)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### protocol.py
|
|
131
|
+
**Purpose:** Provides helpers for A2A protocol-level concerns like topic construction and JSON-RPC
|
|
132
|
+
**Import:** `from solace_agent_mesh.common.a2a.protocol import get_agent_request_topic, create_send_message_request`
|
|
133
|
+
|
|
134
|
+
**Constants/Variables:**
|
|
135
|
+
- `A2A_VERSION: str` - Current A2A protocol version ("v1")
|
|
136
|
+
- `A2A_BASE_PATH: str` - Base path for A2A topics ("a2a/v1")
|
|
137
|
+
|
|
138
|
+
**Functions:**
|
|
139
|
+
- `get_a2a_base_topic(namespace: str) -> str` - Returns base topic prefix for A2A communication
|
|
140
|
+
- `get_discovery_topic(namespace: str) -> str` - Returns topic for agent card discovery
|
|
141
|
+
- `get_agent_request_topic(namespace: str, agent_name: str) -> str` - Returns topic for sending requests to specific agent
|
|
142
|
+
- `get_gateway_status_topic(namespace: str, gateway_id: str, task_id: str) -> str` - Returns topic for publishing status updates to gateway
|
|
143
|
+
- `get_gateway_response_topic(namespace: str, gateway_id: str, task_id: str) -> str` - Returns topic for publishing final response to gateway
|
|
144
|
+
- `get_gateway_status_subscription_topic(namespace: str, self_gateway_id: str) -> str` - Returns wildcard topic for gateway to receive status updates
|
|
145
|
+
- `get_gateway_response_subscription_topic(namespace: str, self_gateway_id: str) -> str` - Returns wildcard topic for gateway to receive responses
|
|
146
|
+
- `get_peer_agent_status_topic(namespace: str, delegating_agent_name: str, sub_task_id: str) -> str` - Returns topic for publishing status to delegating agent
|
|
147
|
+
- `get_agent_response_topic(namespace: str, delegating_agent_name: str, sub_task_id: str) -> str` - Returns topic for publishing response to delegating agent
|
|
148
|
+
- `get_agent_response_subscription_topic(namespace: str, self_agent_name: str) -> str` - Returns wildcard topic for agent to receive responses
|
|
149
|
+
- `get_agent_status_subscription_topic(namespace: str, self_agent_name: str) -> str` - Returns wildcard topic for agent to receive status updates
|
|
150
|
+
- `get_client_response_topic(namespace: str, client_id: str) -> str` - Returns topic for publishing response to client
|
|
151
|
+
- `get_client_status_topic(namespace: str, client_id: str, task_id: str) -> str` - Returns topic for publishing status to client
|
|
152
|
+
- `get_client_status_subscription_topic(namespace: str, client_id: str) -> str` - Returns wildcard topic for client to receive status
|
|
153
|
+
- `create_send_message_request(message: Message, task_id: str, metadata: Optional[Dict[str, Any]] = None) -> SendMessageRequest` - Creates SendMessageRequest object
|
|
154
|
+
- `create_send_streaming_message_request(message: Message, task_id: str, metadata: Optional[Dict[str, Any]] = None) -> SendStreamingMessageRequest` - Creates SendStreamingMessageRequest object
|
|
155
|
+
- `create_success_response(result: Any, request_id: Optional[Union[str, int]]) -> JSONRPCResponse` - Creates successful JSON-RPC response
|
|
156
|
+
- `create_internal_error_response(message: str, request_id: Optional[Union[str, int]], data: Optional[Dict[str, Any]] = None) -> JSONRPCResponse` - Creates internal error response
|
|
157
|
+
- `create_invalid_request_error_response(message: str, request_id: Optional[Union[str, int]], data: Optional[Any] = None) -> JSONRPCResponse` - Creates invalid request error response
|
|
158
|
+
- `create_cancel_task_request(task_id: str) -> CancelTaskRequest` - Creates CancelTaskRequest object
|
|
159
|
+
- `get_request_id(request: A2ARequest) -> str | int` - Gets JSON-RPC request ID
|
|
160
|
+
- `get_request_method(request: A2ARequest) -> str` - Gets JSON-RPC method name
|
|
161
|
+
- `get_message_from_send_request(request: A2ARequest) -> Optional[Message]` - Gets Message from send request
|
|
162
|
+
- `get_task_id_from_cancel_request(request: A2ARequest) -> Optional[str]` - Gets task ID from cancel request
|
|
163
|
+
- `get_response_id(response: JSONRPCResponse) -> Optional[Union[str, int]]` - Gets response ID
|
|
164
|
+
- `get_response_result(response: JSONRPCResponse) -> Optional[Any]` - Gets response result
|
|
165
|
+
- `get_response_error(response: JSONRPCResponse) -> Optional[JSONRPCError]` - Gets response error
|
|
166
|
+
- `topic_matches_subscription(topic: str, subscription: str) -> bool` - Checks if topic matches Solace subscription pattern
|
|
167
|
+
- `subscription_to_regex(subscription: str) -> str` - Converts Solace subscription to regex
|
|
168
|
+
- `extract_task_id_from_topic(topic: str, subscription_pattern: str, log_identifier: str) -> Optional[str]` - Extracts task ID from topic
|
|
169
|
+
|
|
170
|
+
**Usage Examples:**
|
|
171
|
+
```python
|
|
172
|
+
from solace_agent_mesh.common.a2a.protocol import get_agent_request_topic, create_send_message_request
|
|
173
|
+
from solace_agent_mesh.common.a2a.message import create_agent_text_message
|
|
174
|
+
|
|
175
|
+
# Get topic for sending request to an agent
|
|
176
|
+
topic = get_agent_request_topic("my-namespace", "my-agent")
|
|
177
|
+
|
|
178
|
+
# Create a send message request
|
|
179
|
+
message = create_agent_text_message("Hello agent!")
|
|
180
|
+
request = create_sen
|
|
181
|
+
|
|
182
|
+
# content_hash: b894ad5028b63e8ee25dcae4edbefa55a29be08dbece2fd4666ab2f0f0b7d740
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Helpers for creating and consuming A2A Artifact objects.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
import base64
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
from typing import Any, List, Optional, TYPE_CHECKING
|
|
9
|
+
from urllib.parse import urlparse, parse_qs
|
|
10
|
+
|
|
11
|
+
from .types import ContentPart
|
|
12
|
+
from a2a.types import (
|
|
13
|
+
Artifact,
|
|
14
|
+
DataPart,
|
|
15
|
+
FilePart,
|
|
16
|
+
FileWithBytes,
|
|
17
|
+
FileWithUri,
|
|
18
|
+
Part,
|
|
19
|
+
TextPart,
|
|
20
|
+
)
|
|
21
|
+
from solace_ai_connector.common.log import log
|
|
22
|
+
from .. import a2a
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from google.adk.artifacts import BaseArtifactService
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# --- Creation Helpers ---
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def create_text_artifact(
|
|
32
|
+
name: str,
|
|
33
|
+
text: str,
|
|
34
|
+
description: str = "",
|
|
35
|
+
artifact_id: Optional[str] = None,
|
|
36
|
+
) -> Artifact:
|
|
37
|
+
"""
|
|
38
|
+
Creates a new Artifact object containing only a single TextPart.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
name: The human-readable name of the artifact.
|
|
42
|
+
text: The text content of the artifact.
|
|
43
|
+
description: An optional description of the artifact.
|
|
44
|
+
artifact_id: The artifact ID. If None, a new UUID is generated.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
A new `Artifact` object.
|
|
48
|
+
"""
|
|
49
|
+
text_part = TextPart(text=text)
|
|
50
|
+
return Artifact(
|
|
51
|
+
artifact_id=artifact_id or str(uuid.uuid4().hex),
|
|
52
|
+
parts=[Part(root=text_part)],
|
|
53
|
+
name=name,
|
|
54
|
+
description=description,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def create_data_artifact(
|
|
59
|
+
name: str,
|
|
60
|
+
data: dict[str, Any],
|
|
61
|
+
description: str = "",
|
|
62
|
+
artifact_id: Optional[str] = None,
|
|
63
|
+
) -> Artifact:
|
|
64
|
+
"""
|
|
65
|
+
Creates a new Artifact object containing only a single DataPart.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
name: The human-readable name of the artifact.
|
|
69
|
+
data: The structured data content of the artifact.
|
|
70
|
+
description: An optional description of the artifact.
|
|
71
|
+
artifact_id: The artifact ID. If None, a new UUID is generated.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
A new `Artifact` object.
|
|
75
|
+
"""
|
|
76
|
+
data_part = DataPart(data=data)
|
|
77
|
+
return Artifact(
|
|
78
|
+
artifact_id=artifact_id or str(uuid.uuid4().hex),
|
|
79
|
+
parts=[Part(root=data_part)],
|
|
80
|
+
name=name,
|
|
81
|
+
description=description,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def update_artifact_parts(artifact: Artifact, new_parts: List[ContentPart]) -> Artifact:
|
|
86
|
+
"""Returns a new Artifact with its parts replaced."""
|
|
87
|
+
wrapped_parts = [Part(root=p) for p in new_parts]
|
|
88
|
+
return artifact.model_copy(update={"parts": wrapped_parts})
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
async def prepare_file_part_for_publishing(
|
|
92
|
+
part: FilePart,
|
|
93
|
+
mode: str,
|
|
94
|
+
artifact_service: "BaseArtifactService",
|
|
95
|
+
user_id: str,
|
|
96
|
+
session_id: str,
|
|
97
|
+
target_agent_name: str,
|
|
98
|
+
log_identifier: str,
|
|
99
|
+
) -> Optional[FilePart]:
|
|
100
|
+
"""
|
|
101
|
+
Prepares a FilePart for publishing based on the artifact handling mode.
|
|
102
|
+
|
|
103
|
+
- 'ignore': Returns None.
|
|
104
|
+
- 'embed': Ensures the part contains bytes, resolving a URI if necessary.
|
|
105
|
+
- 'reference': Ensures the part contains a URI, saving bytes if necessary.
|
|
106
|
+
- 'passthrough': Returns the part as-is.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
part: The input FilePart, which may contain raw bytes or a URI.
|
|
110
|
+
mode: The artifact handling mode ('ignore', 'embed', 'reference', 'passthrough').
|
|
111
|
+
artifact_service: The ADK artifact service instance.
|
|
112
|
+
user_id: The user ID for the artifact context.
|
|
113
|
+
session_id: The session ID for the artifact context.
|
|
114
|
+
target_agent_name: The name of the agent the artifact will be associated with.
|
|
115
|
+
log_identifier: The logging identifier for log messages.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
The processed FilePart, or None if ignored.
|
|
119
|
+
"""
|
|
120
|
+
log_id = f"{log_identifier}[PrepareFilePart]"
|
|
121
|
+
|
|
122
|
+
if mode == "ignore":
|
|
123
|
+
log.debug("%s Mode is 'ignore', filtering out FilePart.", log_id)
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
if mode == "passthrough":
|
|
127
|
+
log.debug("%s Mode is 'passthrough', returning original FilePart.", log_id)
|
|
128
|
+
return part
|
|
129
|
+
|
|
130
|
+
if mode == "embed":
|
|
131
|
+
if isinstance(part.file, FileWithUri):
|
|
132
|
+
log.debug("%s Mode is 'embed', resolving URI for FilePart.", log_id)
|
|
133
|
+
return await resolve_file_part_uri(part, artifact_service, log_identifier)
|
|
134
|
+
return part # It's already bytes, so it's embedded.
|
|
135
|
+
|
|
136
|
+
if mode == "reference":
|
|
137
|
+
if isinstance(part.file, FileWithBytes):
|
|
138
|
+
if not artifact_service:
|
|
139
|
+
log.warning(
|
|
140
|
+
"%s Mode is 'reference' but no artifact_service is configured. Ignoring FilePart '%s'.",
|
|
141
|
+
log_id,
|
|
142
|
+
part.file.name,
|
|
143
|
+
)
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
filename = part.file.name or f"upload-{uuid.uuid4().hex}"
|
|
148
|
+
content_bytes = base64.b64decode(part.file.bytes)
|
|
149
|
+
mime_type = part.file.mime_type or "application/octet-stream"
|
|
150
|
+
|
|
151
|
+
# Create a concise and accurate metadata dictionary.
|
|
152
|
+
metadata_to_save = {
|
|
153
|
+
"source": log_identifier,
|
|
154
|
+
"description": "This artifact was uploaded via the gateway",
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
# Call the helper with the new, simpler metadata.
|
|
158
|
+
from ...agent.utils.artifact_helpers import save_artifact_with_metadata
|
|
159
|
+
|
|
160
|
+
save_result = await save_artifact_with_metadata(
|
|
161
|
+
artifact_service=artifact_service,
|
|
162
|
+
app_name=target_agent_name,
|
|
163
|
+
user_id=user_id,
|
|
164
|
+
session_id=session_id,
|
|
165
|
+
filename=filename,
|
|
166
|
+
content_bytes=content_bytes,
|
|
167
|
+
mime_type=mime_type,
|
|
168
|
+
metadata_dict=metadata_to_save,
|
|
169
|
+
timestamp=datetime.now(timezone.utc),
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
if save_result["status"] == "success":
|
|
173
|
+
saved_version = save_result.get("data_version")
|
|
174
|
+
from ...agent.utils.artifact_helpers import format_artifact_uri
|
|
175
|
+
|
|
176
|
+
artifact_uri = format_artifact_uri(
|
|
177
|
+
app_name=target_agent_name,
|
|
178
|
+
user_id=user_id,
|
|
179
|
+
session_id=session_id,
|
|
180
|
+
filename=filename,
|
|
181
|
+
version=saved_version,
|
|
182
|
+
)
|
|
183
|
+
ref_part = a2a.create_file_part_from_uri(
|
|
184
|
+
uri=artifact_uri,
|
|
185
|
+
name=filename,
|
|
186
|
+
mime_type=mime_type,
|
|
187
|
+
metadata=part.metadata,
|
|
188
|
+
)
|
|
189
|
+
log.info(
|
|
190
|
+
"%s Converted embedded file '%s' to reference: %s",
|
|
191
|
+
log_id,
|
|
192
|
+
filename,
|
|
193
|
+
artifact_uri,
|
|
194
|
+
)
|
|
195
|
+
return ref_part
|
|
196
|
+
else:
|
|
197
|
+
log.error(
|
|
198
|
+
"%s Failed to save artifact via helper: %s. Skipping FilePart.",
|
|
199
|
+
log_id,
|
|
200
|
+
save_result.get("message"),
|
|
201
|
+
)
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
except Exception as e:
|
|
205
|
+
log.exception(
|
|
206
|
+
"%s Failed to save artifact for reference mode: %s. Skipping FilePart.",
|
|
207
|
+
log_id,
|
|
208
|
+
e,
|
|
209
|
+
)
|
|
210
|
+
return None
|
|
211
|
+
return part # It's already a reference (URI)
|
|
212
|
+
|
|
213
|
+
# Default case if mode is unrecognized
|
|
214
|
+
log.warning(
|
|
215
|
+
"%s Unrecognized artifact_handling_mode '%s'. Ignoring FilePart.", log_id, mode
|
|
216
|
+
)
|
|
217
|
+
return None
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
async def resolve_file_part_uri(
|
|
221
|
+
part: FilePart, artifact_service: "BaseArtifactService", log_identifier: str
|
|
222
|
+
) -> FilePart:
|
|
223
|
+
"""
|
|
224
|
+
Resolves an artifact URI within a FilePart into embedded bytes.
|
|
225
|
+
|
|
226
|
+
If the FilePart does not contain a resolvable `artifact://` URI, it is
|
|
227
|
+
returned unchanged.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
part: The FilePart to resolve.
|
|
231
|
+
artifact_service: The ADK artifact service instance.
|
|
232
|
+
log_identifier: The logging identifier for log messages.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
A FilePart, either with embedded bytes if resolved, or the original part.
|
|
236
|
+
"""
|
|
237
|
+
if not (
|
|
238
|
+
isinstance(part.file, FileWithUri)
|
|
239
|
+
and part.file.uri
|
|
240
|
+
and part.file.uri.startswith("artifact://")
|
|
241
|
+
):
|
|
242
|
+
return part
|
|
243
|
+
|
|
244
|
+
if not artifact_service:
|
|
245
|
+
log.warning(
|
|
246
|
+
"%s Cannot resolve artifact URI, artifact_service is not configured.",
|
|
247
|
+
log_identifier,
|
|
248
|
+
)
|
|
249
|
+
return part
|
|
250
|
+
|
|
251
|
+
uri = part.file.uri
|
|
252
|
+
log_id_prefix = f"{log_identifier}[ResolveURI]"
|
|
253
|
+
try:
|
|
254
|
+
log.info("%s Found artifact URI to resolve: %s", log_id_prefix, uri)
|
|
255
|
+
parsed_uri = urlparse(uri)
|
|
256
|
+
app_name = parsed_uri.netloc
|
|
257
|
+
path_parts = parsed_uri.path.strip("/").split("/")
|
|
258
|
+
|
|
259
|
+
if not app_name or len(path_parts) != 3:
|
|
260
|
+
raise ValueError(
|
|
261
|
+
"Invalid URI structure. Expected artifact://app_name/user_id/session_id/filename"
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
user_id, session_id, filename = path_parts
|
|
265
|
+
version_str = parse_qs(parsed_uri.query).get("version", [None])[0]
|
|
266
|
+
version = int(version_str) if version_str else None
|
|
267
|
+
|
|
268
|
+
from ...agent.utils.artifact_helpers import load_artifact_content_or_metadata
|
|
269
|
+
|
|
270
|
+
loaded_artifact = await load_artifact_content_or_metadata(
|
|
271
|
+
artifact_service=artifact_service,
|
|
272
|
+
app_name=app_name,
|
|
273
|
+
user_id=user_id,
|
|
274
|
+
session_id=session_id,
|
|
275
|
+
filename=filename,
|
|
276
|
+
version=version,
|
|
277
|
+
return_raw_bytes=True,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
if loaded_artifact.get("status") == "success":
|
|
281
|
+
content_bytes = loaded_artifact.get("raw_bytes")
|
|
282
|
+
new_file_content = FileWithBytes(
|
|
283
|
+
bytes=base64.b64encode(content_bytes).decode("utf-8"),
|
|
284
|
+
mime_type=part.file.mime_type,
|
|
285
|
+
name=part.file.name,
|
|
286
|
+
)
|
|
287
|
+
part.file = new_file_content
|
|
288
|
+
log.info(
|
|
289
|
+
"%s Successfully resolved and embedded artifact: %s",
|
|
290
|
+
log_id_prefix,
|
|
291
|
+
uri,
|
|
292
|
+
)
|
|
293
|
+
else:
|
|
294
|
+
log.error(
|
|
295
|
+
"%s Failed to resolve artifact URI '%s': %s",
|
|
296
|
+
log_id_prefix,
|
|
297
|
+
uri,
|
|
298
|
+
loaded_artifact.get("message"),
|
|
299
|
+
)
|
|
300
|
+
except Exception as e:
|
|
301
|
+
log.exception("%s Error resolving artifact URI '%s': %s", log_id_prefix, uri, e)
|
|
302
|
+
return part
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
# --- Consumption Helpers ---
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def get_artifact_id(artifact: Artifact) -> str:
|
|
309
|
+
"""Safely retrieves the ID from an Artifact object."""
|
|
310
|
+
return artifact.artifact_id
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def get_artifact_name(artifact: Artifact) -> Optional[str]:
|
|
314
|
+
"""Safely retrieves the name from an Artifact object."""
|
|
315
|
+
return artifact.name
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def get_parts_from_artifact(artifact: Artifact) -> List[ContentPart]:
|
|
319
|
+
"""
|
|
320
|
+
Extracts the raw, unwrapped Part objects (TextPart, DataPart, etc.) from an Artifact.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
artifact: The `Artifact` object.
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
A list of the unwrapped content parts.
|
|
327
|
+
"""
|
|
328
|
+
return [part.root for part in artifact.parts]
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Helpers for creating and consuming A2A asynchronous event objects, such as
|
|
3
|
+
TaskStatusUpdateEvent and TaskArtifactUpdateEvent.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
from a2a.types import (
|
|
9
|
+
Artifact,
|
|
10
|
+
DataPart,
|
|
11
|
+
Message,
|
|
12
|
+
TaskArtifactUpdateEvent,
|
|
13
|
+
TaskState,
|
|
14
|
+
TaskStatusUpdateEvent,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from . import message as message_helpers
|
|
18
|
+
from . import task as task_helpers
|
|
19
|
+
from ...common.data_parts import SignalData
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# --- Creation Helpers ---
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def create_data_signal_event(
|
|
26
|
+
task_id: str,
|
|
27
|
+
context_id: str,
|
|
28
|
+
signal_data: SignalData,
|
|
29
|
+
agent_name: str,
|
|
30
|
+
part_metadata: Optional[Dict[str, Any]] = None,
|
|
31
|
+
) -> TaskStatusUpdateEvent:
|
|
32
|
+
"""
|
|
33
|
+
Creates a TaskStatusUpdateEvent from a specific signal data model.
|
|
34
|
+
|
|
35
|
+
This is a generalized helper that takes any of the defined SignalData
|
|
36
|
+
types and wraps it in the full A2A event structure.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
task_id: The ID of the task being updated.
|
|
40
|
+
context_id: The context ID for the task.
|
|
41
|
+
signal_data: The Pydantic model for the signal (e.g., ToolInvocationStartData).
|
|
42
|
+
agent_name: The name of the agent sending the signal.
|
|
43
|
+
part_metadata: Optional metadata for the DataPart.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
A new `TaskStatusUpdateEvent` object containing the signal.
|
|
47
|
+
"""
|
|
48
|
+
a2a_message = message_helpers.create_agent_data_message(
|
|
49
|
+
data=signal_data.model_dump(),
|
|
50
|
+
task_id=task_id,
|
|
51
|
+
context_id=context_id,
|
|
52
|
+
part_metadata=part_metadata,
|
|
53
|
+
)
|
|
54
|
+
return create_status_update(
|
|
55
|
+
task_id=task_id,
|
|
56
|
+
context_id=context_id,
|
|
57
|
+
message=a2a_message,
|
|
58
|
+
is_final=False,
|
|
59
|
+
metadata={"agent_name": agent_name},
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def create_status_update(
|
|
64
|
+
task_id: str,
|
|
65
|
+
context_id: str,
|
|
66
|
+
message: Message,
|
|
67
|
+
is_final: bool = False,
|
|
68
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
69
|
+
) -> TaskStatusUpdateEvent:
|
|
70
|
+
"""
|
|
71
|
+
Creates a new TaskStatusUpdateEvent.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
task_id: The ID of the task being updated.
|
|
75
|
+
context_id: The context ID for the task.
|
|
76
|
+
message: The A2AMessage object containing the status details.
|
|
77
|
+
is_final: Whether this is the final update for the task.
|
|
78
|
+
metadata: Optional metadata for the event.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
A new `TaskStatusUpdateEvent` object.
|
|
82
|
+
"""
|
|
83
|
+
task_status = task_helpers.create_task_status(
|
|
84
|
+
state=TaskState.working,
|
|
85
|
+
message=message,
|
|
86
|
+
)
|
|
87
|
+
return TaskStatusUpdateEvent(
|
|
88
|
+
task_id=task_id,
|
|
89
|
+
context_id=context_id,
|
|
90
|
+
status=task_status,
|
|
91
|
+
final=is_final,
|
|
92
|
+
metadata=metadata,
|
|
93
|
+
kind="status-update",
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def create_artifact_update(
|
|
98
|
+
task_id: str,
|
|
99
|
+
context_id: str,
|
|
100
|
+
artifact: Artifact,
|
|
101
|
+
append: bool = False,
|
|
102
|
+
last_chunk: bool = False,
|
|
103
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
104
|
+
) -> TaskArtifactUpdateEvent:
|
|
105
|
+
"""
|
|
106
|
+
Creates a new TaskArtifactUpdateEvent.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
task_id: The ID of the task this artifact belongs to.
|
|
110
|
+
context_id: The context ID for the task.
|
|
111
|
+
artifact: The Artifact object being sent.
|
|
112
|
+
append: If true, the content should be appended to a previous artifact.
|
|
113
|
+
last_chunk: If true, this is the final chunk of the artifact.
|
|
114
|
+
metadata: Optional metadata for the event.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
A new `TaskArtifactUpdateEvent` object.
|
|
118
|
+
"""
|
|
119
|
+
return TaskArtifactUpdateEvent(
|
|
120
|
+
task_id=task_id,
|
|
121
|
+
context_id=context_id,
|
|
122
|
+
artifact=artifact,
|
|
123
|
+
append=append,
|
|
124
|
+
last_chunk=last_chunk,
|
|
125
|
+
metadata=metadata,
|
|
126
|
+
kind="artifact-update",
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# --- Consumption Helpers ---
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def get_message_from_status_update(
|
|
134
|
+
event: TaskStatusUpdateEvent,
|
|
135
|
+
) -> Optional[Message]:
|
|
136
|
+
"""
|
|
137
|
+
Safely extracts the Message object from a TaskStatusUpdateEvent.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
event: The TaskStatusUpdateEvent object.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
The `Message` object if present, otherwise None.
|
|
144
|
+
"""
|
|
145
|
+
if event and event.status:
|
|
146
|
+
return event.status.message
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def get_data_parts_from_status_update(
|
|
151
|
+
event: TaskStatusUpdateEvent,
|
|
152
|
+
) -> List[DataPart]:
|
|
153
|
+
"""
|
|
154
|
+
Safely extracts all DataPart objects from a TaskStatusUpdateEvent's message.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
event: The TaskStatusUpdateEvent object.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
A list of `DataPart` objects found, or an empty list.
|
|
161
|
+
"""
|
|
162
|
+
message = get_message_from_status_update(event)
|
|
163
|
+
if not message:
|
|
164
|
+
return []
|
|
165
|
+
|
|
166
|
+
return message_helpers.get_data_parts_from_message(message)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def get_artifact_from_artifact_update(
|
|
170
|
+
event: TaskArtifactUpdateEvent,
|
|
171
|
+
) -> Optional[Artifact]:
|
|
172
|
+
"""
|
|
173
|
+
Safely extracts the Artifact object from a TaskArtifactUpdateEvent.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
event: The TaskArtifactUpdateEvent object.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
The `Artifact` object if present, otherwise None.
|
|
180
|
+
"""
|
|
181
|
+
if event:
|
|
182
|
+
return event.artifact
|
|
183
|
+
return None
|