zyndai-agent 0.1.5__py3-none-any.whl → 0.2.2__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.
- zyndai_agent/__init__.py +22 -0
- zyndai_agent/agent.py +143 -34
- zyndai_agent/config_manager.py +153 -0
- zyndai_agent/message.py +112 -0
- zyndai_agent/search.py +146 -24
- zyndai_agent/webhook_communication.py +470 -0
- {zyndai_agent-0.1.5.dist-info → zyndai_agent-0.2.2.dist-info}/METADATA +458 -63
- zyndai_agent-0.2.2.dist-info/RECORD +14 -0
- {zyndai_agent-0.1.5.dist-info → zyndai_agent-0.2.2.dist-info}/WHEEL +1 -1
- zyndai_agent-0.1.5.dist-info/RECORD +0 -11
- {zyndai_agent-0.1.5.dist-info → zyndai_agent-0.2.2.dist-info}/top_level.txt +0 -0
zyndai_agent/__init__.py
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from zyndai_agent.agent import ZyndAIAgent, AgentConfig
|
|
2
|
+
from zyndai_agent.communication import AgentCommunicationManager, MQTTMessage
|
|
3
|
+
from zyndai_agent.webhook_communication import WebhookCommunicationManager
|
|
4
|
+
from zyndai_agent.message import AgentMessage
|
|
5
|
+
from zyndai_agent.search import SearchAndDiscoveryManager, AgentSearchResponse
|
|
6
|
+
from zyndai_agent.identity import IdentityManager
|
|
7
|
+
from zyndai_agent.payment import X402PaymentProcessor
|
|
8
|
+
from zyndai_agent.config_manager import ConfigManager
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"ZyndAIAgent",
|
|
12
|
+
"AgentConfig",
|
|
13
|
+
"AgentCommunicationManager",
|
|
14
|
+
"WebhookCommunicationManager",
|
|
15
|
+
"MQTTMessage",
|
|
16
|
+
"AgentMessage",
|
|
17
|
+
"SearchAndDiscoveryManager",
|
|
18
|
+
"AgentSearchResponse",
|
|
19
|
+
"IdentityManager",
|
|
20
|
+
"X402PaymentProcessor",
|
|
21
|
+
"ConfigManager",
|
|
22
|
+
]
|
zyndai_agent/agent.py
CHANGED
|
@@ -3,56 +3,102 @@ import requests
|
|
|
3
3
|
from zyndai_agent.search import SearchAndDiscoveryManager
|
|
4
4
|
from zyndai_agent.identity import IdentityManager
|
|
5
5
|
from zyndai_agent.communication import AgentCommunicationManager
|
|
6
|
+
from zyndai_agent.webhook_communication import WebhookCommunicationManager
|
|
6
7
|
from zyndai_agent.payment import X402PaymentProcessor
|
|
8
|
+
from zyndai_agent.config_manager import ConfigManager
|
|
7
9
|
from pydantic import BaseModel
|
|
8
10
|
from typing import Optional
|
|
9
11
|
from langchain.agents import create_agent
|
|
10
12
|
from langgraph.graph.state import CompiledStateGraph
|
|
11
13
|
|
|
12
14
|
class AgentConfig(BaseModel):
|
|
15
|
+
name: str = ""
|
|
16
|
+
description: str = ""
|
|
17
|
+
capabilities: Optional[dict] = None
|
|
18
|
+
|
|
13
19
|
auto_reconnect: bool = True
|
|
14
20
|
message_history_limit: int = 100
|
|
15
21
|
registry_url: str = "http://localhost:3002"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
|
|
23
|
+
# Webhook configuration (new)
|
|
24
|
+
webhook_host: Optional[str] = "0.0.0.0"
|
|
25
|
+
webhook_port: Optional[int] = 5000
|
|
26
|
+
webhook_url: Optional[str] = None # Public URL if behind NAT
|
|
27
|
+
api_key: Optional[str] = None # API key for webhook registration
|
|
28
|
+
|
|
29
|
+
# MQTT configuration (deprecated, kept for backward compatibility)
|
|
30
|
+
mqtt_broker_url: Optional[str] = None
|
|
19
31
|
default_outbox_topic: Optional[str] = None
|
|
20
|
-
secret_seed: str = None
|
|
21
32
|
|
|
22
|
-
|
|
33
|
+
price: Optional[str] = None
|
|
23
34
|
|
|
24
|
-
|
|
35
|
+
# Config directory for agent identity (allows multiple agents in same project)
|
|
36
|
+
config_dir: Optional[str] = None # e.g., ".agent-stock" or ".agent-user"
|
|
37
|
+
|
|
38
|
+
class ZyndAIAgent(SearchAndDiscoveryManager, IdentityManager, X402PaymentProcessor, WebhookCommunicationManager):
|
|
39
|
+
|
|
40
|
+
def __init__(self, agent_config: AgentConfig):
|
|
25
41
|
|
|
26
42
|
self.agent_executor: CompiledStateGraph = None
|
|
27
|
-
self.agent_config = agent_config
|
|
28
|
-
self.
|
|
43
|
+
self.agent_config = agent_config
|
|
44
|
+
self.communication_mode = None # Track which mode is active
|
|
45
|
+
|
|
46
|
+
# Load or create agent config from .agent/config.json
|
|
47
|
+
config = ConfigManager.load_or_create(agent_config)
|
|
48
|
+
self.registry_agent_id = config["id"]
|
|
49
|
+
self.agent_id = config["id"]
|
|
50
|
+
self.secret_seed = config["seed"]
|
|
51
|
+
self.identity_credential = config["did"]
|
|
29
52
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
raise FileNotFoundError(f"Identity credential file not found at {agent_config.identity_credential_path}")
|
|
35
|
-
|
|
36
|
-
IdentityManager.__init__(self,agent_config.registry_url)
|
|
53
|
+
self.x402_processor = X402PaymentProcessor(self.secret_seed)
|
|
54
|
+
self.pay_to_address = self.x402_processor.account.address
|
|
55
|
+
|
|
56
|
+
IdentityManager.__init__(self, agent_config.registry_url)
|
|
37
57
|
|
|
38
58
|
SearchAndDiscoveryManager.__init__(
|
|
39
59
|
self,
|
|
40
60
|
registry_url=agent_config.registry_url
|
|
41
61
|
)
|
|
42
|
-
|
|
43
|
-
AgentCommunicationManager.__init__(
|
|
44
|
-
self,
|
|
45
|
-
self.identity_credential["issuer"],
|
|
46
|
-
default_inbox_topic=f"{self.identity_credential['issuer']}/inbox",
|
|
47
|
-
default_outbox_topic=agent_config.default_outbox_topic,
|
|
48
|
-
auto_reconnect=True,
|
|
49
|
-
message_history_limit=agent_config.message_history_limit,
|
|
50
|
-
identity_credential=self.identity_credential,
|
|
51
|
-
secret_seed=agent_config.secret_seed,
|
|
52
|
-
mqtt_broker_url=agent_config.mqtt_broker_url
|
|
53
|
-
)
|
|
54
62
|
|
|
55
|
-
|
|
63
|
+
# Determine communication mode: webhook or MQTT
|
|
64
|
+
# Prefer webhook if webhook_port is configured
|
|
65
|
+
if agent_config.webhook_port is not None and agent_config.mqtt_broker_url is None:
|
|
66
|
+
# Use webhook mode
|
|
67
|
+
self.communication_mode = "webhook"
|
|
68
|
+
WebhookCommunicationManager.__init__(
|
|
69
|
+
self,
|
|
70
|
+
agent_id=self.identity_credential["issuer"],
|
|
71
|
+
webhook_host=agent_config.webhook_host,
|
|
72
|
+
webhook_port=agent_config.webhook_port,
|
|
73
|
+
webhook_url=agent_config.webhook_url,
|
|
74
|
+
auto_restart=agent_config.auto_reconnect,
|
|
75
|
+
message_history_limit=agent_config.message_history_limit,
|
|
76
|
+
identity_credential=self.identity_credential,
|
|
77
|
+
price=agent_config.price,
|
|
78
|
+
pay_to_address=self.pay_to_address
|
|
79
|
+
)
|
|
80
|
+
self.update_agent_webhook_info()
|
|
81
|
+
|
|
82
|
+
elif agent_config.mqtt_broker_url is not None:
|
|
83
|
+
# Use MQTT mode (backward compatibility)
|
|
84
|
+
self.communication_mode = "mqtt"
|
|
85
|
+
AgentCommunicationManager.__init__(
|
|
86
|
+
self,
|
|
87
|
+
self.identity_credential["issuer"],
|
|
88
|
+
default_inbox_topic=f"{self.identity_credential['issuer']}/inbox",
|
|
89
|
+
default_outbox_topic=agent_config.default_outbox_topic,
|
|
90
|
+
auto_reconnect=True,
|
|
91
|
+
message_history_limit=agent_config.message_history_limit,
|
|
92
|
+
identity_credential=self.identity_credential,
|
|
93
|
+
secret_seed=self.secret_seed,
|
|
94
|
+
mqtt_broker_url=agent_config.mqtt_broker_url
|
|
95
|
+
)
|
|
96
|
+
self.update_agent_mqtt_info()
|
|
97
|
+
else:
|
|
98
|
+
raise ValueError("Either webhook_port or mqtt_broker_url must be configured")
|
|
99
|
+
|
|
100
|
+
# Display agent info
|
|
101
|
+
self._display_agent_info()
|
|
56
102
|
|
|
57
103
|
|
|
58
104
|
|
|
@@ -62,16 +108,79 @@ class ZyndAIAgent(SearchAndDiscoveryManager, IdentityManager, AgentCommunication
|
|
|
62
108
|
|
|
63
109
|
def update_agent_mqtt_info(self):
|
|
64
110
|
"""Updates the mqtt connection info of the agent into the registry so other agents can find me"""
|
|
65
|
-
|
|
66
|
-
updateResponse = requests.
|
|
67
|
-
f"{self.agent_config.registry_url}/agents/update-mqtt",
|
|
111
|
+
|
|
112
|
+
updateResponse = requests.patch(
|
|
113
|
+
f"{self.agent_config.registry_url}/agents/update-mqtt",
|
|
68
114
|
data={
|
|
69
|
-
"seed": self.
|
|
115
|
+
"seed": self.secret_seed,
|
|
70
116
|
"mqttUri": self.agent_config.mqtt_broker_url
|
|
71
117
|
}
|
|
72
118
|
)
|
|
73
|
-
|
|
119
|
+
|
|
74
120
|
if (updateResponse.status_code != 201):
|
|
75
121
|
raise Exception("Failed to update agent connection info in p3 registry.")
|
|
76
122
|
|
|
77
|
-
print("Synced with the registry...")
|
|
123
|
+
print("Synced with the registry...")
|
|
124
|
+
|
|
125
|
+
def update_agent_webhook_info(self):
|
|
126
|
+
"""Updates the webhook URL of the agent into the registry so other agents can find me"""
|
|
127
|
+
if not self.agent_config.api_key:
|
|
128
|
+
raise ValueError("API key is required for webhook registration. Please provide api_key in AgentConfig.")
|
|
129
|
+
|
|
130
|
+
headers = {
|
|
131
|
+
"accept": "*/*",
|
|
132
|
+
"X-API-KEY": self.agent_config.api_key
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
payload = {
|
|
136
|
+
"agentId": self.registry_agent_id,
|
|
137
|
+
"httpWebhookUrl": self.webhook_url
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
print(f"Updating webhook URL: {payload}")
|
|
141
|
+
|
|
142
|
+
updateResponse = requests.patch(
|
|
143
|
+
f"{self.agent_config.registry_url}/agents/update-webhook",
|
|
144
|
+
json=payload,
|
|
145
|
+
headers=headers
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
if updateResponse.status_code != 200:
|
|
149
|
+
raise Exception(f"Failed to update agent webhook info in Zynd registry. Status: {updateResponse.status_code}, Response: {updateResponse.text}")
|
|
150
|
+
|
|
151
|
+
print("Synced webhook URL with the registry...")
|
|
152
|
+
|
|
153
|
+
def _display_agent_info(self):
|
|
154
|
+
"""Display agent information in a pretty format on startup."""
|
|
155
|
+
name = self.agent_config.name or "Unnamed Agent"
|
|
156
|
+
description = self.agent_config.description or "-"
|
|
157
|
+
agent_id = self.agent_id
|
|
158
|
+
address = self.pay_to_address
|
|
159
|
+
did = self.identity_credential.get("issuer", "-")
|
|
160
|
+
mode = self.communication_mode or "-"
|
|
161
|
+
webhook_url = getattr(self, "webhook_url", None)
|
|
162
|
+
price = self.agent_config.price or "Free"
|
|
163
|
+
|
|
164
|
+
border = "=" * 60
|
|
165
|
+
print(f"\n{border}")
|
|
166
|
+
print(f" ZYND AI AGENT")
|
|
167
|
+
print(f"{border}")
|
|
168
|
+
print(f" Name : {name}")
|
|
169
|
+
print(f" Description : {description}")
|
|
170
|
+
print(f" Agent ID : {agent_id}")
|
|
171
|
+
print(f" DID : {did}")
|
|
172
|
+
print(f" Address : {address}")
|
|
173
|
+
print(f" Mode : {mode}")
|
|
174
|
+
if webhook_url:
|
|
175
|
+
print(f" Webhook URL : {webhook_url}")
|
|
176
|
+
print(f" Price : {price}")
|
|
177
|
+
print(f"{border}\n")
|
|
178
|
+
|
|
179
|
+
def update_agent_connection_info(self):
|
|
180
|
+
"""Updates the agent connection info (webhook or MQTT) in the registry based on communication mode"""
|
|
181
|
+
if self.communication_mode == "webhook":
|
|
182
|
+
self.update_agent_webhook_info()
|
|
183
|
+
elif self.communication_mode == "mqtt":
|
|
184
|
+
self.update_agent_mqtt_info()
|
|
185
|
+
else:
|
|
186
|
+
raise ValueError(f"Unknown communication mode: {self.communication_mode}")
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ConfigManager:
|
|
7
|
+
"""
|
|
8
|
+
Manages agent configuration stored in .agent/config.json.
|
|
9
|
+
|
|
10
|
+
On first run, provisions a new agent via the registry API and saves
|
|
11
|
+
the identity credentials locally. On subsequent runs, loads the
|
|
12
|
+
saved config so the user doesn't need to provide identity credentials manually.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
DEFAULT_CONFIG_DIR = ".agent"
|
|
16
|
+
CONFIG_FILE = "config.json"
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def _config_path(config_dir: str = None):
|
|
20
|
+
dir_name = config_dir or ConfigManager.DEFAULT_CONFIG_DIR
|
|
21
|
+
return os.path.join(os.getcwd(), dir_name, ConfigManager.CONFIG_FILE)
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def _config_dir(config_dir: str = None):
|
|
25
|
+
dir_name = config_dir or ConfigManager.DEFAULT_CONFIG_DIR
|
|
26
|
+
return os.path.join(os.getcwd(), dir_name)
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def load_config(config_dir: str = None):
|
|
30
|
+
"""Load existing config from .agent/config.json. Returns None if not found."""
|
|
31
|
+
config_path = ConfigManager._config_path(config_dir)
|
|
32
|
+
if not os.path.exists(config_path):
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
with open(config_path, "r") as f:
|
|
36
|
+
config = json.load(f)
|
|
37
|
+
|
|
38
|
+
print(f"Loaded agent config from {config_path}")
|
|
39
|
+
return config
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def save_config(config: dict, config_dir: str = None):
|
|
43
|
+
"""Save config to .agent/config.json, creating the directory if needed."""
|
|
44
|
+
dir_path = ConfigManager._config_dir(config_dir)
|
|
45
|
+
os.makedirs(dir_path, exist_ok=True)
|
|
46
|
+
|
|
47
|
+
config_path = ConfigManager._config_path(config_dir)
|
|
48
|
+
with open(config_path, "w") as f:
|
|
49
|
+
json.dump(config, f, indent=2)
|
|
50
|
+
|
|
51
|
+
print(f"Saved agent config to {config_path}")
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def create_agent(registry_url: str, api_key: str, name: str, description: str, capabilities: dict, config_dir: str = None):
|
|
55
|
+
"""
|
|
56
|
+
Create a new agent via the registry API.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
registry_url: Base URL of the agent registry
|
|
60
|
+
api_key: API key for authentication
|
|
61
|
+
name: Agent display name
|
|
62
|
+
description: Agent description
|
|
63
|
+
capabilities: Agent capabilities dict (e.g. {"ai": ["nlp"], "protocols": ["http"]})
|
|
64
|
+
config_dir: Custom config directory (e.g., ".agent-stock")
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
dict: The saved config with id, didIdentifier, did, name, description, seed
|
|
68
|
+
"""
|
|
69
|
+
headers = {
|
|
70
|
+
"accept": "application/json",
|
|
71
|
+
"Content-Type": "application/json",
|
|
72
|
+
"x-api-key": api_key
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
payload = {
|
|
76
|
+
"name": name,
|
|
77
|
+
"description": description,
|
|
78
|
+
"capabilities": capabilities,
|
|
79
|
+
"status": "ACTIVE"
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
response = requests.post(
|
|
83
|
+
f"{registry_url}/agents",
|
|
84
|
+
json=payload,
|
|
85
|
+
headers=headers
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
if response.status_code not in (200, 201):
|
|
89
|
+
raise RuntimeError(
|
|
90
|
+
f"Failed to create agent via registry API. "
|
|
91
|
+
f"Status: {response.status_code}, Response: {response.text}"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
data = response.json()
|
|
95
|
+
|
|
96
|
+
# The 'did' field in the API response is a JSON string; parse it
|
|
97
|
+
did = data["did"]
|
|
98
|
+
if isinstance(did, str):
|
|
99
|
+
did = json.loads(did)
|
|
100
|
+
|
|
101
|
+
config = {
|
|
102
|
+
"id": data["id"],
|
|
103
|
+
"didIdentifier": data["didIdentifier"],
|
|
104
|
+
"did": did,
|
|
105
|
+
"name": data["name"],
|
|
106
|
+
"description": data["description"],
|
|
107
|
+
"seed": data["seed"]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
ConfigManager.save_config(config, config_dir)
|
|
111
|
+
return config
|
|
112
|
+
|
|
113
|
+
@staticmethod
|
|
114
|
+
def load_or_create(agent_config):
|
|
115
|
+
"""
|
|
116
|
+
Load existing agent config or create a new agent.
|
|
117
|
+
|
|
118
|
+
If .agent/config.json exists, returns stored values.
|
|
119
|
+
Otherwise, calls the registry API to provision a new agent.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
agent_config: AgentConfig instance with registry_url, api_key, name,
|
|
123
|
+
description, capabilities, and optional config_dir
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
dict with keys: id, didIdentifier, did, name, description, seed
|
|
127
|
+
"""
|
|
128
|
+
config_dir = getattr(agent_config, 'config_dir', None)
|
|
129
|
+
config = ConfigManager.load_config(config_dir)
|
|
130
|
+
if config is not None:
|
|
131
|
+
return config
|
|
132
|
+
|
|
133
|
+
# Validate required fields for agent creation
|
|
134
|
+
if not agent_config.api_key:
|
|
135
|
+
raise ValueError(
|
|
136
|
+
"api_key is required in AgentConfig to create a new agent. "
|
|
137
|
+
"Provide an API key or place a .agent/config.json in the working directory."
|
|
138
|
+
)
|
|
139
|
+
if not agent_config.name:
|
|
140
|
+
raise ValueError("name is required in AgentConfig to create a new agent.")
|
|
141
|
+
if not agent_config.capabilities:
|
|
142
|
+
raise ValueError("capabilities is required in AgentConfig to create a new agent.")
|
|
143
|
+
|
|
144
|
+
dir_name = config_dir or ConfigManager.DEFAULT_CONFIG_DIR
|
|
145
|
+
print(f"No {dir_name}/config.json found. Creating a new agent...")
|
|
146
|
+
return ConfigManager.create_agent(
|
|
147
|
+
registry_url=agent_config.registry_url,
|
|
148
|
+
api_key=agent_config.api_key,
|
|
149
|
+
name=agent_config.name,
|
|
150
|
+
description=agent_config.description,
|
|
151
|
+
capabilities=agent_config.capabilities,
|
|
152
|
+
config_dir=config_dir
|
|
153
|
+
)
|
zyndai_agent/message.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import json
|
|
3
|
+
import uuid
|
|
4
|
+
import logging
|
|
5
|
+
from typing import Optional, Dict, Any
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
class AgentMessage:
|
|
10
|
+
"""
|
|
11
|
+
Structured message format for agent communication.
|
|
12
|
+
|
|
13
|
+
This class provides a standardized way to format, serialize, and deserialize
|
|
14
|
+
messages exchanged between agents, with support for conversation threading,
|
|
15
|
+
message types, and metadata.
|
|
16
|
+
|
|
17
|
+
Protocol-agnostic: Can be used with MQTT, HTTP webhooks, or other transports.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
content: str,
|
|
23
|
+
sender_id: str,
|
|
24
|
+
sender_did: dict = None,
|
|
25
|
+
receiver_id: Optional[str] = None,
|
|
26
|
+
message_type: str = "query",
|
|
27
|
+
message_id: Optional[str] = None,
|
|
28
|
+
conversation_id: Optional[str] = None,
|
|
29
|
+
in_reply_to: Optional[str] = None,
|
|
30
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
31
|
+
):
|
|
32
|
+
"""
|
|
33
|
+
Initialize a new agent message.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
content: The main message content
|
|
37
|
+
sender_id: Identifier for the message sender
|
|
38
|
+
sender_did: DID credential of the sender
|
|
39
|
+
receiver_id: Identifier for the intended recipient (None for broadcasts)
|
|
40
|
+
message_type: Type categorization ("query", "response", "broadcast", "system")
|
|
41
|
+
message_id: Unique identifier for this message (auto-generated if None)
|
|
42
|
+
conversation_id: ID grouping related messages (auto-generated if None)
|
|
43
|
+
in_reply_to: ID of the message this is responding to (None if not a reply)
|
|
44
|
+
metadata: Additional contextual information
|
|
45
|
+
"""
|
|
46
|
+
self.content = content
|
|
47
|
+
self.sender_id = sender_id
|
|
48
|
+
self.receiver_id = receiver_id
|
|
49
|
+
self.sender_did = sender_did
|
|
50
|
+
self.message_type = message_type
|
|
51
|
+
self.message_id = message_id or str(uuid.uuid4())
|
|
52
|
+
self.conversation_id = conversation_id or str(uuid.uuid4())
|
|
53
|
+
self.in_reply_to = in_reply_to
|
|
54
|
+
self.metadata = metadata or {}
|
|
55
|
+
self.timestamp = time.time()
|
|
56
|
+
|
|
57
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
58
|
+
"""Convert message to dictionary format."""
|
|
59
|
+
return {
|
|
60
|
+
"content": self.content,
|
|
61
|
+
"prompt": self.content,
|
|
62
|
+
"sender_id": self.sender_id,
|
|
63
|
+
"sender_did": self.sender_did,
|
|
64
|
+
"receiver_id": self.receiver_id,
|
|
65
|
+
"message_type": self.message_type,
|
|
66
|
+
"message_id": self.message_id,
|
|
67
|
+
"conversation_id": self.conversation_id,
|
|
68
|
+
"in_reply_to": self.in_reply_to,
|
|
69
|
+
"metadata": self.metadata,
|
|
70
|
+
"timestamp": self.timestamp
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
def to_json(self) -> str:
|
|
74
|
+
"""Convert message to JSON string for transmission."""
|
|
75
|
+
return json.dumps(self.to_dict())
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def from_dict(cls, data: Dict[str, Any]) -> 'AgentMessage':
|
|
79
|
+
"""Create message object from dictionary data."""
|
|
80
|
+
return cls(
|
|
81
|
+
content=data.get("prompt", data.get("content", "")),
|
|
82
|
+
sender_id=data.get("sender_id", "unknown"),
|
|
83
|
+
sender_did=data.get("sender_did", "unknown"),
|
|
84
|
+
receiver_id=data.get("receiver_id"),
|
|
85
|
+
message_type=data.get("message_type", "query"),
|
|
86
|
+
message_id=data.get("message_id"),
|
|
87
|
+
conversation_id=data.get("conversation_id"),
|
|
88
|
+
in_reply_to=data.get("in_reply_to"),
|
|
89
|
+
metadata=data.get("metadata", {})
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def from_json(cls, json_str: str) -> 'AgentMessage':
|
|
94
|
+
"""
|
|
95
|
+
Create message object from JSON string.
|
|
96
|
+
|
|
97
|
+
Handles both valid JSON and fallback for plain text messages.
|
|
98
|
+
"""
|
|
99
|
+
try:
|
|
100
|
+
data = json.loads(json_str)
|
|
101
|
+
return cls.from_dict(data)
|
|
102
|
+
except json.JSONDecodeError as e:
|
|
103
|
+
logger.error(f"Failed to parse message as JSON: {e}")
|
|
104
|
+
return cls(
|
|
105
|
+
content=json_str,
|
|
106
|
+
sender_id="unknown",
|
|
107
|
+
message_type="raw"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# Backward compatibility alias
|
|
112
|
+
MQTTMessage = AgentMessage
|