zyndai-agent 0.2.1__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 +2 -0
- zyndai_agent/agent.py +54 -21
- zyndai_agent/config_manager.py +153 -0
- zyndai_agent/message.py +2 -1
- zyndai_agent/search.py +145 -24
- zyndai_agent/webhook_communication.py +15 -11
- {zyndai_agent-0.2.1.dist-info → zyndai_agent-0.2.2.dist-info}/METADATA +292 -55
- zyndai_agent-0.2.2.dist-info/RECORD +14 -0
- {zyndai_agent-0.2.1.dist-info → zyndai_agent-0.2.2.dist-info}/WHEEL +1 -1
- zyndai_agent-0.2.1.dist-info/RECORD +0 -13
- {zyndai_agent-0.2.1.dist-info → zyndai_agent-0.2.2.dist-info}/top_level.txt +0 -0
zyndai_agent/__init__.py
CHANGED
|
@@ -5,6 +5,7 @@ from zyndai_agent.message import AgentMessage
|
|
|
5
5
|
from zyndai_agent.search import SearchAndDiscoveryManager, AgentSearchResponse
|
|
6
6
|
from zyndai_agent.identity import IdentityManager
|
|
7
7
|
from zyndai_agent.payment import X402PaymentProcessor
|
|
8
|
+
from zyndai_agent.config_manager import ConfigManager
|
|
8
9
|
|
|
9
10
|
__all__ = [
|
|
10
11
|
"ZyndAIAgent",
|
|
@@ -17,4 +18,5 @@ __all__ = [
|
|
|
17
18
|
"AgentSearchResponse",
|
|
18
19
|
"IdentityManager",
|
|
19
20
|
"X402PaymentProcessor",
|
|
21
|
+
"ConfigManager",
|
|
20
22
|
]
|
zyndai_agent/agent.py
CHANGED
|
@@ -5,12 +5,17 @@ from zyndai_agent.identity import IdentityManager
|
|
|
5
5
|
from zyndai_agent.communication import AgentCommunicationManager
|
|
6
6
|
from zyndai_agent.webhook_communication import WebhookCommunicationManager
|
|
7
7
|
from zyndai_agent.payment import X402PaymentProcessor
|
|
8
|
+
from zyndai_agent.config_manager import ConfigManager
|
|
8
9
|
from pydantic import BaseModel
|
|
9
10
|
from typing import Optional
|
|
10
11
|
from langchain.agents import create_agent
|
|
11
12
|
from langgraph.graph.state import CompiledStateGraph
|
|
12
13
|
|
|
13
14
|
class AgentConfig(BaseModel):
|
|
15
|
+
name: str = ""
|
|
16
|
+
description: str = ""
|
|
17
|
+
capabilities: Optional[dict] = None
|
|
18
|
+
|
|
14
19
|
auto_reconnect: bool = True
|
|
15
20
|
message_history_limit: int = 100
|
|
16
21
|
registry_url: str = "http://localhost:3002"
|
|
@@ -25,14 +30,10 @@ class AgentConfig(BaseModel):
|
|
|
25
30
|
mqtt_broker_url: Optional[str] = None
|
|
26
31
|
default_outbox_topic: Optional[str] = None
|
|
27
32
|
|
|
28
|
-
# Common configuration
|
|
29
|
-
identity_credential_path: str
|
|
30
|
-
identity_credential: Optional[dict] = None
|
|
31
|
-
secret_seed: str = None
|
|
32
|
-
agent_id: str = None
|
|
33
|
-
|
|
34
33
|
price: Optional[str] = None
|
|
35
|
-
|
|
34
|
+
|
|
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"
|
|
36
37
|
|
|
37
38
|
class ZyndAIAgent(SearchAndDiscoveryManager, IdentityManager, X402PaymentProcessor, WebhookCommunicationManager):
|
|
38
39
|
|
|
@@ -40,14 +41,17 @@ class ZyndAIAgent(SearchAndDiscoveryManager, IdentityManager, X402PaymentProcess
|
|
|
40
41
|
|
|
41
42
|
self.agent_executor: CompiledStateGraph = None
|
|
42
43
|
self.agent_config = agent_config
|
|
43
|
-
self.x402_processor = X402PaymentProcessor(agent_config.secret_seed)
|
|
44
44
|
self.communication_mode = None # Track which mode is active
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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"]
|
|
52
|
+
|
|
53
|
+
self.x402_processor = X402PaymentProcessor(self.secret_seed)
|
|
54
|
+
self.pay_to_address = self.x402_processor.account.address
|
|
51
55
|
|
|
52
56
|
IdentityManager.__init__(self, agent_config.registry_url)
|
|
53
57
|
|
|
@@ -71,7 +75,7 @@ class ZyndAIAgent(SearchAndDiscoveryManager, IdentityManager, X402PaymentProcess
|
|
|
71
75
|
message_history_limit=agent_config.message_history_limit,
|
|
72
76
|
identity_credential=self.identity_credential,
|
|
73
77
|
price=agent_config.price,
|
|
74
|
-
pay_to_address=
|
|
78
|
+
pay_to_address=self.pay_to_address
|
|
75
79
|
)
|
|
76
80
|
self.update_agent_webhook_info()
|
|
77
81
|
|
|
@@ -86,13 +90,16 @@ class ZyndAIAgent(SearchAndDiscoveryManager, IdentityManager, X402PaymentProcess
|
|
|
86
90
|
auto_reconnect=True,
|
|
87
91
|
message_history_limit=agent_config.message_history_limit,
|
|
88
92
|
identity_credential=self.identity_credential,
|
|
89
|
-
secret_seed=
|
|
93
|
+
secret_seed=self.secret_seed,
|
|
90
94
|
mqtt_broker_url=agent_config.mqtt_broker_url
|
|
91
95
|
)
|
|
92
96
|
self.update_agent_mqtt_info()
|
|
93
97
|
else:
|
|
94
98
|
raise ValueError("Either webhook_port or mqtt_broker_url must be configured")
|
|
95
99
|
|
|
100
|
+
# Display agent info
|
|
101
|
+
self._display_agent_info()
|
|
102
|
+
|
|
96
103
|
|
|
97
104
|
|
|
98
105
|
def set_agent_executor(self, agent_executor: CompiledStateGraph):
|
|
@@ -105,7 +112,7 @@ class ZyndAIAgent(SearchAndDiscoveryManager, IdentityManager, X402PaymentProcess
|
|
|
105
112
|
updateResponse = requests.patch(
|
|
106
113
|
f"{self.agent_config.registry_url}/agents/update-mqtt",
|
|
107
114
|
data={
|
|
108
|
-
"seed": self.
|
|
115
|
+
"seed": self.secret_seed,
|
|
109
116
|
"mqttUri": self.agent_config.mqtt_broker_url
|
|
110
117
|
}
|
|
111
118
|
)
|
|
@@ -120,18 +127,18 @@ class ZyndAIAgent(SearchAndDiscoveryManager, IdentityManager, X402PaymentProcess
|
|
|
120
127
|
if not self.agent_config.api_key:
|
|
121
128
|
raise ValueError("API key is required for webhook registration. Please provide api_key in AgentConfig.")
|
|
122
129
|
|
|
123
|
-
# Prepare headers with API key
|
|
124
130
|
headers = {
|
|
125
|
-
"
|
|
126
|
-
"
|
|
131
|
+
"accept": "*/*",
|
|
132
|
+
"X-API-KEY": self.agent_config.api_key
|
|
127
133
|
}
|
|
128
134
|
|
|
129
|
-
# Prepare request body
|
|
130
135
|
payload = {
|
|
131
|
-
"agentId": self.
|
|
136
|
+
"agentId": self.registry_agent_id,
|
|
132
137
|
"httpWebhookUrl": self.webhook_url
|
|
133
138
|
}
|
|
134
139
|
|
|
140
|
+
print(f"Updating webhook URL: {payload}")
|
|
141
|
+
|
|
135
142
|
updateResponse = requests.patch(
|
|
136
143
|
f"{self.agent_config.registry_url}/agents/update-webhook",
|
|
137
144
|
json=payload,
|
|
@@ -143,6 +150,32 @@ class ZyndAIAgent(SearchAndDiscoveryManager, IdentityManager, X402PaymentProcess
|
|
|
143
150
|
|
|
144
151
|
print("Synced webhook URL with the registry...")
|
|
145
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
|
+
|
|
146
179
|
def update_agent_connection_info(self):
|
|
147
180
|
"""Updates the agent connection info (webhook or MQTT) in the registry based on communication mode"""
|
|
148
181
|
if self.communication_mode == "webhook":
|
|
@@ -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
CHANGED
|
@@ -58,6 +58,7 @@ class AgentMessage:
|
|
|
58
58
|
"""Convert message to dictionary format."""
|
|
59
59
|
return {
|
|
60
60
|
"content": self.content,
|
|
61
|
+
"prompt": self.content,
|
|
61
62
|
"sender_id": self.sender_id,
|
|
62
63
|
"sender_did": self.sender_did,
|
|
63
64
|
"receiver_id": self.receiver_id,
|
|
@@ -77,7 +78,7 @@ class AgentMessage:
|
|
|
77
78
|
def from_dict(cls, data: Dict[str, Any]) -> 'AgentMessage':
|
|
78
79
|
"""Create message object from dictionary data."""
|
|
79
80
|
return cls(
|
|
80
|
-
content=data.get("content", ""),
|
|
81
|
+
content=data.get("prompt", data.get("content", "")),
|
|
81
82
|
sender_id=data.get("sender_id", "unknown"),
|
|
82
83
|
sender_did=data.get("sender_did", "unknown"),
|
|
83
84
|
receiver_id=data.get("receiver_id"),
|
zyndai_agent/search.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Agent Discovery and Search Protocol Module for ZyndAI
|
|
2
2
|
import logging
|
|
3
3
|
import requests
|
|
4
|
+
from urllib.parse import urlencode
|
|
4
5
|
|
|
5
6
|
from typing import List, Optional, TypedDict
|
|
6
7
|
|
|
@@ -11,54 +12,174 @@ logging.basicConfig(
|
|
|
11
12
|
)
|
|
12
13
|
logger = logging.getLogger("SearchAndDiscovery")
|
|
13
14
|
|
|
15
|
+
|
|
14
16
|
class AgentSearchResponse(TypedDict):
|
|
15
17
|
id: str
|
|
16
18
|
name: str
|
|
17
19
|
description: str
|
|
18
20
|
mqttUri: Optional[str] # Deprecated, kept for backward compatibility
|
|
19
|
-
|
|
21
|
+
httpWebhookUrl: Optional[str] # Field for webhook communication
|
|
20
22
|
inboxTopic: Optional[str]
|
|
21
|
-
|
|
23
|
+
capabilities: Optional[dict]
|
|
24
|
+
status: Optional[str]
|
|
22
25
|
didIdentifier: str
|
|
23
|
-
did:
|
|
26
|
+
did: str # JSON string of DID credential
|
|
27
|
+
|
|
24
28
|
|
|
25
29
|
class SearchAndDiscoveryManager:
|
|
26
30
|
"""
|
|
27
31
|
This class implements the search and discovery protocol for ZyndAI agents.
|
|
28
32
|
It allows agents to discover each other and share information about their capabilities.
|
|
29
|
-
"""
|
|
30
33
|
|
|
31
|
-
|
|
34
|
+
The search uses semantic matching via the keyword parameter, allowing for
|
|
35
|
+
fuzzy/vague searches across agent names, descriptions, and capabilities.
|
|
36
|
+
"""
|
|
32
37
|
|
|
38
|
+
def __init__(self, registry_url: str = "http://localhost:3002"):
|
|
33
39
|
self.agents = []
|
|
34
40
|
self.registry_url = registry_url
|
|
35
41
|
|
|
42
|
+
def search_agents(
|
|
43
|
+
self,
|
|
44
|
+
keyword: Optional[str] = None,
|
|
45
|
+
name: Optional[str] = None,
|
|
46
|
+
capabilities: Optional[List[str]] = None,
|
|
47
|
+
status: Optional[str] = None,
|
|
48
|
+
did: Optional[str] = None,
|
|
49
|
+
limit: int = 10,
|
|
50
|
+
offset: int = 0
|
|
51
|
+
) -> List[AgentSearchResponse]:
|
|
52
|
+
"""
|
|
53
|
+
Search for agents in the registry using various filters.
|
|
54
|
+
|
|
55
|
+
The keyword parameter supports semantic search across name, description,
|
|
56
|
+
capabilities, and metadata fields.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
keyword: Semantic search term (searches across name, description, capabilities, metadata)
|
|
60
|
+
name: Filter by agent name (case-insensitive, partial match)
|
|
61
|
+
capabilities: List of capabilities to filter by
|
|
62
|
+
status: Filter by agent status (e.g., "ACTIVE")
|
|
63
|
+
did: Filter by exact DID match
|
|
64
|
+
limit: Maximum number of results to return (default: 10, max: 100)
|
|
65
|
+
offset: Number of results to skip for pagination (default: 0)
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
List of matching agents
|
|
69
|
+
"""
|
|
70
|
+
logger.info(f"Searching agents with keyword='{keyword}', capabilities={capabilities}")
|
|
71
|
+
|
|
72
|
+
# Build query parameters
|
|
73
|
+
params = {}
|
|
74
|
+
|
|
75
|
+
if keyword:
|
|
76
|
+
params["keyword"] = keyword
|
|
77
|
+
if name:
|
|
78
|
+
params["name"] = name
|
|
79
|
+
if capabilities:
|
|
80
|
+
params["capabilities"] = ",".join(capabilities)
|
|
81
|
+
if status:
|
|
82
|
+
params["status"] = status
|
|
83
|
+
if did:
|
|
84
|
+
params["did"] = did
|
|
85
|
+
|
|
86
|
+
params["limit"] = limit
|
|
87
|
+
params["offset"] = offset
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
url = f"{self.registry_url}/agents"
|
|
91
|
+
logger.info(f"GET {url}?{urlencode(params)}")
|
|
92
|
+
|
|
93
|
+
resp = requests.get(url, params=params)
|
|
94
|
+
|
|
95
|
+
if resp.status_code == 200:
|
|
96
|
+
response_data = resp.json()
|
|
97
|
+
# API returns { data: [...], count: N, total: N }
|
|
98
|
+
agents = response_data.get("data", [])
|
|
99
|
+
total = response_data.get("total", len(agents))
|
|
100
|
+
logger.info(f"Found {len(agents)} agents (total: {total}).")
|
|
101
|
+
return agents
|
|
102
|
+
else:
|
|
103
|
+
logger.error(f"Failed to search agents: {resp.status_code} - {resp.text}")
|
|
104
|
+
return []
|
|
105
|
+
|
|
106
|
+
except requests.RequestException as e:
|
|
107
|
+
logger.error(f"Request failed: {e}")
|
|
108
|
+
return []
|
|
109
|
+
|
|
110
|
+
def search_agents_by_capabilities(
|
|
111
|
+
self,
|
|
112
|
+
capabilities: List[str] = [],
|
|
113
|
+
top_k: Optional[int] = None
|
|
114
|
+
) -> List[AgentSearchResponse]:
|
|
115
|
+
"""
|
|
116
|
+
Discover agents based on capabilities using semantic keyword search.
|
|
117
|
+
|
|
118
|
+
This method converts capabilities into a keyword search query for
|
|
119
|
+
semantic matching across the registry.
|
|
36
120
|
|
|
37
|
-
|
|
121
|
+
Args:
|
|
122
|
+
capabilities: List of capability terms to search for
|
|
123
|
+
top_k: Maximum number of results to return
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
List of matching agents
|
|
38
127
|
"""
|
|
39
|
-
|
|
128
|
+
logger.info(f"Discovering agents by capabilities: {capabilities}")
|
|
129
|
+
|
|
130
|
+
# Convert capabilities list into a semantic search keyword
|
|
131
|
+
# Join capabilities into a search phrase for semantic matching
|
|
132
|
+
keyword = " ".join(capabilities) if capabilities else None
|
|
133
|
+
|
|
134
|
+
limit = top_k if top_k is not None else 10
|
|
135
|
+
|
|
136
|
+
return self.search_agents(
|
|
137
|
+
keyword=keyword,
|
|
138
|
+
limit=limit
|
|
139
|
+
)
|
|
40
140
|
|
|
41
|
-
|
|
42
|
-
|
|
141
|
+
def search_agents_by_keyword(
|
|
142
|
+
self,
|
|
143
|
+
keyword: str,
|
|
144
|
+
limit: int = 10,
|
|
145
|
+
offset: int = 0
|
|
146
|
+
) -> List[AgentSearchResponse]:
|
|
43
147
|
"""
|
|
148
|
+
Search for agents using a semantic keyword search.
|
|
44
149
|
|
|
45
|
-
|
|
150
|
+
The keyword is matched against agent name, description, capabilities,
|
|
151
|
+
and metadata using semantic search.
|
|
46
152
|
|
|
153
|
+
Args:
|
|
154
|
+
keyword: Search term for semantic matching
|
|
155
|
+
limit: Maximum number of results (default: 10)
|
|
156
|
+
offset: Pagination offset (default: 0)
|
|
47
157
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
158
|
+
Returns:
|
|
159
|
+
List of matching agents
|
|
160
|
+
"""
|
|
161
|
+
return self.search_agents(keyword=keyword, limit=limit, offset=offset)
|
|
52
162
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
]
|
|
163
|
+
def get_agent_by_id(self, agent_id: str) -> Optional[AgentSearchResponse]:
|
|
164
|
+
"""
|
|
165
|
+
Get a specific agent by its ID.
|
|
57
166
|
|
|
58
|
-
|
|
59
|
-
|
|
167
|
+
Args:
|
|
168
|
+
agent_id: The unique identifier of the agent
|
|
60
169
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
170
|
+
Returns:
|
|
171
|
+
Agent details or None if not found
|
|
172
|
+
"""
|
|
173
|
+
try:
|
|
174
|
+
url = f"{self.registry_url}/agents/{agent_id}"
|
|
175
|
+
resp = requests.get(url)
|
|
176
|
+
|
|
177
|
+
if resp.status_code == 200:
|
|
178
|
+
return resp.json()
|
|
179
|
+
else:
|
|
180
|
+
logger.error(f"Failed to get agent {agent_id}: {resp.status_code}")
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
except requests.RequestException as e:
|
|
184
|
+
logger.error(f"Request failed: {e}")
|
|
185
|
+
return None
|
|
@@ -234,10 +234,11 @@ class WebhookCommunicationManager:
|
|
|
234
234
|
# Update actual port used
|
|
235
235
|
self.webhook_port = port
|
|
236
236
|
|
|
237
|
-
#
|
|
237
|
+
# Auto-form webhook URL from host and port
|
|
238
238
|
if self.webhook_url is None:
|
|
239
|
-
|
|
240
|
-
|
|
239
|
+
host = "localhost" if self.webhook_host == "0.0.0.0" else self.webhook_host
|
|
240
|
+
scheme = "https" if port == 443 else "http"
|
|
241
|
+
self.webhook_url = f"{scheme}://{host}:{port}/webhook"
|
|
241
242
|
|
|
242
243
|
self.is_running = True
|
|
243
244
|
server_started = True
|
|
@@ -301,10 +302,11 @@ class WebhookCommunicationManager:
|
|
|
301
302
|
sender_did=self.identity_credential
|
|
302
303
|
)
|
|
303
304
|
|
|
304
|
-
# Convert to
|
|
305
|
-
|
|
305
|
+
# Convert to dict for JSON serialization
|
|
306
|
+
# Note: use to_dict() not to_json() - json= parameter expects a dict
|
|
307
|
+
json_payload = message.to_dict()
|
|
306
308
|
|
|
307
|
-
# Send HTTP POST request with
|
|
309
|
+
# Send HTTP POST request with JSON body
|
|
308
310
|
response = requests.post(
|
|
309
311
|
self.target_webhook_url,
|
|
310
312
|
json=json_payload,
|
|
@@ -455,12 +457,14 @@ class WebhookCommunicationManager:
|
|
|
455
457
|
Connect to another agent using their webhook URL.
|
|
456
458
|
|
|
457
459
|
Args:
|
|
458
|
-
agent: Agent search response containing
|
|
460
|
+
agent: Agent search response containing httpWebhookUrl
|
|
459
461
|
"""
|
|
460
|
-
|
|
461
|
-
|
|
462
|
+
# Support both old 'webhookUrl' and new 'httpWebhookUrl' field names
|
|
463
|
+
webhook_url = agent.get("httpWebhookUrl") or agent.get("webhookUrl")
|
|
464
|
+
if not webhook_url:
|
|
465
|
+
raise ValueError("Agent does not have httpWebhookUrl. Cannot connect via webhook.")
|
|
462
466
|
|
|
463
|
-
self.target_webhook_url =
|
|
467
|
+
self.target_webhook_url = webhook_url
|
|
464
468
|
self.is_agent_connected = True
|
|
465
469
|
|
|
466
|
-
logger.info(f"Connected to agent {agent
|
|
470
|
+
logger.info(f"Connected to agent {agent.get('didIdentifier', 'unknown')} at {self.target_webhook_url}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: zyndai-agent
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: A Langchain and Autogen wrapper that enables agents to communicate and establish identity on the Zynd AI Network. This SDK provides three core capabilities: Identity Management, Agent Discovery & Search, and HTTP Webhook or MQTT-based Communication.
|
|
5
5
|
Author-email: Swapnil Shinde <swapnilshinde9382@gmail.com>
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -23,8 +23,13 @@ A powerful Python SDK that enables AI agents to communicate securely and discove
|
|
|
23
23
|
- 🔐 **Secure Identity Management**: Verify and manage agent identities using Polygon ID credentials
|
|
24
24
|
- 🔍 **Smart Agent Discovery**: Search and discover agents based on their capabilities with ML-powered semantic matching
|
|
25
25
|
- 💬 **Flexible Communication**: Choose between HTTP Webhooks or MQTT for encrypted real-time messaging between agents
|
|
26
|
+
- **Async/Sync Webhooks**: Support both fire-and-forget and request-response patterns
|
|
27
|
+
- **Built-in Endpoints**: `/webhook` (async) and `/webhook/sync` (sync with 30s timeout)
|
|
26
28
|
- 🤖 **LangChain Integration**: Seamlessly works with LangChain agents and any LLM
|
|
27
29
|
- 💰 **x402 Micropayments**: Built-in support for pay-per-use API endpoints with automatic payment handling
|
|
30
|
+
- **Webhook Protection**: Enable x402 payments on agent webhook endpoints
|
|
31
|
+
- **HTTP x402 Client**: Make payments to external x402-protected APIs
|
|
32
|
+
- **Automatic Challenge/Response**: Seamless payment flow with no manual intervention
|
|
28
33
|
- 🌐 **Decentralized Network**: Connect to the global ZyndAI agent network
|
|
29
34
|
- ⚡ **Easy Setup**: Get started in minutes with simple configuration
|
|
30
35
|
|
|
@@ -352,6 +357,8 @@ The SDK supports two communication modes: **HTTP Webhooks** (recommended) and **
|
|
|
352
357
|
|
|
353
358
|
Each agent runs an embedded Flask server to receive webhook requests. This mode is simpler, doesn't require external MQTT brokers, and works well for most use cases.
|
|
354
359
|
|
|
360
|
+
##### Basic Webhook Configuration
|
|
361
|
+
|
|
355
362
|
```python
|
|
356
363
|
from zyndai_agent.agent import AgentConfig, ZyndAIAgent
|
|
357
364
|
import os
|
|
@@ -386,13 +393,177 @@ result = zyndai_agent.send_message(
|
|
|
386
393
|
messages = zyndai_agent.read_messages()
|
|
387
394
|
```
|
|
388
395
|
|
|
396
|
+
##### Webhook with x402 Micropayments
|
|
397
|
+
|
|
398
|
+
Enable x402 payment protection on your webhook endpoints to monetize agent services:
|
|
399
|
+
|
|
400
|
+
```python
|
|
401
|
+
from zyndai_agent.agent import AgentConfig, ZyndAIAgent
|
|
402
|
+
import os
|
|
403
|
+
|
|
404
|
+
# Configure with webhook mode and x402 payments
|
|
405
|
+
agent_config = AgentConfig(
|
|
406
|
+
webhook_host="0.0.0.0",
|
|
407
|
+
webhook_port=5001,
|
|
408
|
+
webhook_url=None, # Auto-generated http://localhost:5001/webhook
|
|
409
|
+
auto_reconnect=True,
|
|
410
|
+
message_history_limit=100,
|
|
411
|
+
registry_url="https://registry.zynd.ai",
|
|
412
|
+
identity_credential_path="./identity_credential.json",
|
|
413
|
+
secret_seed=os.environ["AGENT_SEED"],
|
|
414
|
+
agent_id=os.environ["AGENT_ID"],
|
|
415
|
+
price="$0.01", # Price per request
|
|
416
|
+
pay_to_address="0xYourEthereumAddress", # Your payment address
|
|
417
|
+
api_key=os.environ["API_KEY"]
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
# Agent automatically starts webhook server with x402 payment middleware
|
|
421
|
+
zyndai_agent = ZyndAIAgent(agent_config=agent_config)
|
|
422
|
+
print(f"Webhook URL: {zyndai_agent.webhook_url}")
|
|
423
|
+
print("x402 payments enabled - clients must pay to interact")
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
**x402 Configuration:**
|
|
427
|
+
- `price`: Payment amount per request (e.g., "$0.01", "$0.10")
|
|
428
|
+
- `pay_to_address`: Your Ethereum address to receive payments
|
|
429
|
+
- If both `price` and `pay_to_address` are provided, x402 is automatically enabled
|
|
430
|
+
- If either is `None`, x402 is disabled and endpoints are free to access
|
|
431
|
+
|
|
432
|
+
##### Asynchronous vs Synchronous Webhooks
|
|
433
|
+
|
|
434
|
+
The SDK supports two webhook communication modes:
|
|
435
|
+
|
|
436
|
+
**1. Asynchronous Mode (Default)** - Fire and forget:
|
|
437
|
+
```python
|
|
438
|
+
# Messages sent to /webhook endpoint
|
|
439
|
+
# Returns immediately without waiting for agent processing
|
|
440
|
+
result = zyndai_agent.send_message("Process this data")
|
|
441
|
+
|
|
442
|
+
# Handler processes asynchronously
|
|
443
|
+
def message_handler(message: AgentMessage, topic: str):
|
|
444
|
+
# Process message
|
|
445
|
+
response = process_message(message.content)
|
|
446
|
+
|
|
447
|
+
# Optionally send response via separate webhook call
|
|
448
|
+
if zyndai_agent.target_webhook_url:
|
|
449
|
+
zyndai_agent.send_message(response)
|
|
450
|
+
|
|
451
|
+
zyndai_agent.add_message_handler(message_handler)
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
**2. Synchronous Mode** - Request/response pattern:
|
|
455
|
+
```python
|
|
456
|
+
# Messages sent to /webhook/sync endpoint
|
|
457
|
+
# Waits for agent to process and return response (30s timeout)
|
|
458
|
+
|
|
459
|
+
# Handler sets response using set_response()
|
|
460
|
+
def message_handler(message: AgentMessage, topic: str):
|
|
461
|
+
# Process message
|
|
462
|
+
response = agent_executor.invoke({"input": message.content})
|
|
463
|
+
|
|
464
|
+
# Set response for synchronous caller
|
|
465
|
+
zyndai_agent.set_response(message.message_id, response["output"])
|
|
466
|
+
|
|
467
|
+
zyndai_agent.add_message_handler(message_handler)
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
**Synchronous Response Flow:**
|
|
471
|
+
1. Client sends POST to `/webhook/sync`
|
|
472
|
+
2. Agent processes message via handler
|
|
473
|
+
3. Handler calls `set_response(message_id, response_content)`
|
|
474
|
+
4. Client receives immediate HTTP response with result
|
|
475
|
+
5. Timeout after 30 seconds if no response
|
|
476
|
+
|
|
477
|
+
**Use Cases:**
|
|
478
|
+
- **Async**: Long-running tasks, notifications, fire-and-forget operations
|
|
479
|
+
- **Sync**: Real-time queries, immediate responses needed, request-reply pattern
|
|
480
|
+
|
|
481
|
+
##### Complete Webhook Example with LangChain
|
|
482
|
+
|
|
483
|
+
```python
|
|
484
|
+
from zyndai_agent.agent import AgentConfig, ZyndAIAgent
|
|
485
|
+
from zyndai_agent.message import AgentMessage
|
|
486
|
+
from langchain_openai import ChatOpenAI
|
|
487
|
+
from langchain_classic.memory import ChatMessageHistory
|
|
488
|
+
from langchain_classic.agents import AgentExecutor, create_tool_calling_agent
|
|
489
|
+
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
|
490
|
+
from langchain_community.tools.tavily_search import TavilySearchResults
|
|
491
|
+
import os
|
|
492
|
+
|
|
493
|
+
# Configure agent with webhook and x402
|
|
494
|
+
agent_config = AgentConfig(
|
|
495
|
+
webhook_host="0.0.0.0",
|
|
496
|
+
webhook_port=5001,
|
|
497
|
+
webhook_url=None,
|
|
498
|
+
auto_reconnect=True,
|
|
499
|
+
message_history_limit=100,
|
|
500
|
+
registry_url="https://registry.zynd.ai",
|
|
501
|
+
identity_credential_path="./identity_credential.json",
|
|
502
|
+
secret_seed=os.environ["AGENT_SEED"],
|
|
503
|
+
agent_id=os.environ["AGENT_ID"],
|
|
504
|
+
price="$0.01", # Enable x402 payments
|
|
505
|
+
pay_to_address="0xYourAddress",
|
|
506
|
+
api_key=os.environ["API_KEY"]
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
# Initialize agent
|
|
510
|
+
zynd_agent = ZyndAIAgent(agent_config=agent_config)
|
|
511
|
+
|
|
512
|
+
# Create LangChain agent
|
|
513
|
+
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
|
|
514
|
+
search_tool = TavilySearchResults(max_results=3)
|
|
515
|
+
message_history = ChatMessageHistory()
|
|
516
|
+
|
|
517
|
+
prompt = ChatPromptTemplate.from_messages([
|
|
518
|
+
("system", "You are a helpful AI agent with web search capabilities."),
|
|
519
|
+
MessagesPlaceholder(variable_name="chat_history"),
|
|
520
|
+
("human", "{input}"),
|
|
521
|
+
MessagesPlaceholder(variable_name="agent_scratchpad")
|
|
522
|
+
])
|
|
523
|
+
|
|
524
|
+
agent = create_tool_calling_agent(llm, [search_tool], prompt)
|
|
525
|
+
agent_executor = AgentExecutor(agent=agent, tools=[search_tool], verbose=True)
|
|
526
|
+
zynd_agent.set_agent_executor(agent_executor)
|
|
527
|
+
|
|
528
|
+
# Message handler for both sync and async
|
|
529
|
+
def message_handler(message: AgentMessage, topic: str):
|
|
530
|
+
# Add to history
|
|
531
|
+
message_history.add_user_message(message.content)
|
|
532
|
+
|
|
533
|
+
# Process with LangChain agent
|
|
534
|
+
agent_response = zynd_agent.agent_executor.invoke({
|
|
535
|
+
"input": message.content,
|
|
536
|
+
"chat_history": message_history.messages
|
|
537
|
+
})
|
|
538
|
+
agent_output = agent_response["output"]
|
|
539
|
+
|
|
540
|
+
message_history.add_ai_message(agent_output)
|
|
541
|
+
|
|
542
|
+
# Set response for synchronous mode
|
|
543
|
+
zynd_agent.set_response(message.message_id, agent_output)
|
|
544
|
+
|
|
545
|
+
# Also send via webhook for agent-to-agent communication
|
|
546
|
+
if zynd_agent.target_webhook_url:
|
|
547
|
+
zynd_agent.send_message(agent_output)
|
|
548
|
+
|
|
549
|
+
zynd_agent.add_message_handler(message_handler)
|
|
550
|
+
|
|
551
|
+
print(f"\nWebhook Agent Running!")
|
|
552
|
+
print(f"Webhook URL: {zynd_agent.webhook_url}")
|
|
553
|
+
print(f"x402 Payments: Enabled at {agent_config.price}")
|
|
554
|
+
print("Supports both /webhook (async) and /webhook/sync (sync) endpoints")
|
|
555
|
+
```
|
|
556
|
+
|
|
389
557
|
**Webhook Mode Features:**
|
|
390
558
|
- ✅ No external broker required
|
|
391
559
|
- ✅ Standard HTTP/HTTPS communication
|
|
560
|
+
- ✅ Synchronous and asynchronous message patterns
|
|
561
|
+
- ✅ x402 micropayments integration
|
|
392
562
|
- ✅ Easy to deploy and debug
|
|
393
563
|
- ✅ Works behind firewalls with port forwarding
|
|
394
564
|
- ✅ Auto-retry on port conflicts (tries ports 5000-5010)
|
|
395
565
|
- ✅ Built-in health check endpoint (`/health`)
|
|
566
|
+
- ✅ Automatic payment challenge/response handling
|
|
396
567
|
|
|
397
568
|
#### MQTT Mode (Legacy)
|
|
398
569
|
|
|
@@ -424,6 +595,24 @@ result = zyndai_agent.send_message(
|
|
|
424
595
|
**Migration from MQTT to Webhooks:**
|
|
425
596
|
To migrate existing agents, simply change your configuration from `mqtt_broker_url` to `webhook_host` and `webhook_port`. All other code remains the same!
|
|
426
597
|
|
|
598
|
+
#### Webhook Endpoints Summary
|
|
599
|
+
|
|
600
|
+
When you start a webhook-enabled agent, the following HTTP endpoints become available:
|
|
601
|
+
|
|
602
|
+
| Endpoint | Method | Description | Response Time |
|
|
603
|
+
|----------|--------|-------------|---------------|
|
|
604
|
+
| `/webhook` | POST | Asynchronous message reception | Immediate (fire-and-forget) |
|
|
605
|
+
| `/webhook/sync` | POST | Synchronous message with response | Waits up to 30s for agent response |
|
|
606
|
+
| `/health` | GET | Health check and status | Immediate |
|
|
607
|
+
|
|
608
|
+
**Endpoint Behaviors:**
|
|
609
|
+
- **`/webhook`** (Async): Accepts message, returns 200 immediately, processes in background
|
|
610
|
+
- **`/webhook/sync`** (Sync): Accepts message, waits for handler to call `set_response()`, returns response or timeout
|
|
611
|
+
- **`/health`**: Returns agent status, useful for monitoring and discovery
|
|
612
|
+
|
|
613
|
+
**x402 Protection:**
|
|
614
|
+
When `price` and `pay_to_address` are configured, all webhook endpoints require x402 payment before processing requests.
|
|
615
|
+
|
|
427
616
|
### 🔐 Identity Verification
|
|
428
617
|
|
|
429
618
|
Verify other agents' identities before trusting them:
|
|
@@ -643,6 +832,9 @@ while True:
|
|
|
643
832
|
| `webhook_port` | `int` | `5000` | **Webhook mode**: Port number for webhook server |
|
|
644
833
|
| `webhook_url` | `str` | `None` | **Webhook mode**: Public URL (auto-generated if None) |
|
|
645
834
|
| `api_key` | `str` | `None` | **Webhook mode**: API key for webhook registration (required for webhook mode) |
|
|
835
|
+
| `price` | `str` | `None` | **x402 Webhook**: Price per request (e.g., "$0.01"). Enables x402 if set with `pay_to_address` |
|
|
836
|
+
| `pay_to_address` | `str` | `None` | **x402 Webhook**: Ethereum address for payments. Enables x402 if set with `price` |
|
|
837
|
+
| `agent_id` | `str` | `None` | **x402 Webhook**: Agent identifier (required when using x402 payments) |
|
|
646
838
|
| `mqtt_broker_url` | `str` | `None` | **MQTT mode**: MQTT broker connection URL |
|
|
647
839
|
| `default_outbox_topic` | `str` | `None` | **MQTT mode**: Default topic for outgoing messages |
|
|
648
840
|
| `auto_reconnect` | `bool` | `True` | Auto-reconnect/restart on disconnect |
|
|
@@ -651,9 +843,11 @@ while True:
|
|
|
651
843
|
| `identity_credential_path` | `str` | Required | Path to your DID credential file |
|
|
652
844
|
| `secret_seed` | `str` | Required | Your agent's secret seed |
|
|
653
845
|
|
|
654
|
-
**
|
|
655
|
-
- Configure either `webhook_port` (recommended) OR `mqtt_broker_url`, not both
|
|
656
|
-
- When using webhook mode, `api_key` is required for registering your webhook URL with the registry
|
|
846
|
+
**Notes**:
|
|
847
|
+
- Configure either `webhook_port` (recommended) OR `mqtt_broker_url`, not both
|
|
848
|
+
- When using webhook mode, `api_key` is required for registering your webhook URL with the registry
|
|
849
|
+
- x402 payments require both `price` and `pay_to_address` to be set. If either is `None`, x402 is disabled
|
|
850
|
+
- When using x402, `agent_id` should also be provided for proper identification
|
|
657
851
|
|
|
658
852
|
### Message Types
|
|
659
853
|
|
|
@@ -827,54 +1021,67 @@ except Exception as e:
|
|
|
827
1021
|
|
|
828
1022
|
## 📊 Architecture Overview
|
|
829
1023
|
```
|
|
830
|
-
|
|
831
|
-
│ ZyndAI Agent SDK
|
|
832
|
-
|
|
833
|
-
│
|
|
834
|
-
│ ┌──────────────────┐ ┌──────────────────┐
|
|
835
|
-
│ │ Identity Manager │ │ Search Manager │
|
|
836
|
-
│ │ │ │ │
|
|
837
|
-
│ │ - Verify DIDs │ │ - Capability │
|
|
838
|
-
│ │ - Load Creds │ │ Matching │
|
|
839
|
-
│ │ - Manage Keys │ │ - ML Scoring │
|
|
840
|
-
│ └──────────────────┘ └──────────────────┘
|
|
841
|
-
│
|
|
842
|
-
│
|
|
843
|
-
│ │ Communication Manager
|
|
844
|
-
│ │
|
|
845
|
-
│ │ -
|
|
846
|
-
│ │ -
|
|
847
|
-
│ │ -
|
|
848
|
-
│ │ -
|
|
849
|
-
│
|
|
850
|
-
│
|
|
851
|
-
│
|
|
852
|
-
│
|
|
853
|
-
│ │
|
|
854
|
-
│ │
|
|
855
|
-
│ │
|
|
856
|
-
│ │ -
|
|
857
|
-
│ │ -
|
|
858
|
-
│
|
|
859
|
-
│
|
|
860
|
-
│
|
|
861
|
-
│
|
|
862
|
-
│ │
|
|
863
|
-
│ │
|
|
864
|
-
│ │
|
|
865
|
-
│ │ -
|
|
866
|
-
│
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1024
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
1025
|
+
│ ZyndAI Agent SDK │
|
|
1026
|
+
├─────────────────────────────────────────────────────────────┤
|
|
1027
|
+
│ │
|
|
1028
|
+
│ ┌──────────────────┐ ┌──────────────────┐ │
|
|
1029
|
+
│ │ Identity Manager │ │ Search Manager │ │
|
|
1030
|
+
│ │ │ │ │ │
|
|
1031
|
+
│ │ - Verify DIDs │ │ - Capability │ │
|
|
1032
|
+
│ │ - Load Creds │ │ Matching │ │
|
|
1033
|
+
│ │ - Manage Keys │ │ - ML Scoring │ │
|
|
1034
|
+
│ └──────────────────┘ └──────────────────┘ │
|
|
1035
|
+
│ │
|
|
1036
|
+
│ ┌──────────────────────────────────────────────┐ │
|
|
1037
|
+
│ │ Webhook Communication Manager │ │
|
|
1038
|
+
│ │ │ │
|
|
1039
|
+
│ │ - Embedded Flask Server │ │
|
|
1040
|
+
│ │ - Async Endpoint (/webhook) │ │
|
|
1041
|
+
│ │ - Sync Endpoint (/webhook/sync) │ │
|
|
1042
|
+
│ │ - Health Check (/health) │ │
|
|
1043
|
+
│ │ - x402 Payment Middleware (optional) │ │
|
|
1044
|
+
│ │ - Message History Tracking │ │
|
|
1045
|
+
│ └──────────────────────────────────────────────┘ │
|
|
1046
|
+
│ │
|
|
1047
|
+
│ ┌──────────────────────────────────────────┐ │
|
|
1048
|
+
│ │ Communication Manager (MQTT - Legacy) │ │
|
|
1049
|
+
│ │ │ │
|
|
1050
|
+
│ │ - End-to-End Encryption (ECIES) │ │
|
|
1051
|
+
│ │ - Message Routing │ │
|
|
1052
|
+
│ │ - Topic Management │ │
|
|
1053
|
+
│ │ - History Tracking │ │
|
|
1054
|
+
│ └──────────────────────────────────────────┘ │
|
|
1055
|
+
│ │
|
|
1056
|
+
│ ┌──────────────────────────────────────────┐ │
|
|
1057
|
+
│ │ x402 Payment Processor │ │
|
|
1058
|
+
│ │ │ │
|
|
1059
|
+
│ │ - Payment Challenge Handling │ │
|
|
1060
|
+
│ │ - Signature Generation │ │
|
|
1061
|
+
│ │ - Automatic Retry Logic │ │
|
|
1062
|
+
│ │ - Multi-Method Support (GET/POST/etc) │ │
|
|
1063
|
+
│ │ - Webhook Protection (via middleware) │ │
|
|
1064
|
+
│ └──────────────────────────────────────────┘ │
|
|
1065
|
+
│ │
|
|
1066
|
+
│ ┌──────────────────────────────────────────┐ │
|
|
1067
|
+
│ │ LangChain Integration │ │
|
|
1068
|
+
│ │ │ │
|
|
1069
|
+
│ │ - Agent Executor Support │ │
|
|
1070
|
+
│ │ - Custom Tools │ │
|
|
1071
|
+
│ │ - Memory Management │ │
|
|
1072
|
+
│ └──────────────────────────────────────────┘ │
|
|
1073
|
+
└─────────────────────────────────────────────────────────────┘
|
|
1074
|
+
▼ ▼ ▼
|
|
1075
|
+
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
1076
|
+
│ Registry │ │ MQTT Broker │ │ Other Agent │
|
|
1077
|
+
│ Service │ │ (Legacy) │ │ Webhooks │
|
|
1078
|
+
└──────────────┘ └──────────────┘ └──────────────┘
|
|
1079
|
+
▼
|
|
1080
|
+
┌──────────────┐
|
|
1081
|
+
│ x402 Enabled │
|
|
1082
|
+
│ Services │
|
|
1083
|
+
│ (HTTP APIs) │
|
|
1084
|
+
└──────────────┘
|
|
878
1085
|
```
|
|
879
1086
|
|
|
880
1087
|
## 🤝 Contributing
|
|
@@ -975,6 +1182,35 @@ curl http://localhost:5000/health
|
|
|
975
1182
|
# {"status": "ok", "agent_id": "did:polygonid:...", "timestamp": 1234567890}
|
|
976
1183
|
```
|
|
977
1184
|
|
|
1185
|
+
**Testing Webhook Endpoints**
|
|
1186
|
+
```bash
|
|
1187
|
+
# Test async webhook (returns immediately)
|
|
1188
|
+
curl -X POST http://localhost:5000/webhook \
|
|
1189
|
+
-H "Content-Type: application/json" \
|
|
1190
|
+
-d '{"content": "Hello", "sender_id": "test", "message_type": "query"}'
|
|
1191
|
+
|
|
1192
|
+
# Response: {"status": "received", "message_id": "...", "timestamp": 1234567890}
|
|
1193
|
+
|
|
1194
|
+
# Test sync webhook (waits for agent response)
|
|
1195
|
+
curl -X POST http://localhost:5000/webhook/sync \
|
|
1196
|
+
-H "Content-Type: application/json" \
|
|
1197
|
+
-d '{"content": "Hello", "sender_id": "test", "message_type": "query"}'
|
|
1198
|
+
|
|
1199
|
+
# Response: {"status": "success", "message_id": "...", "response": "...", "timestamp": 1234567890}
|
|
1200
|
+
# Or timeout: {"status": "timeout", "message_id": "...", "error": "...", "timestamp": 1234567890}
|
|
1201
|
+
```
|
|
1202
|
+
|
|
1203
|
+
**x402 Payment Testing**
|
|
1204
|
+
```bash
|
|
1205
|
+
# First request triggers 402 Payment Required
|
|
1206
|
+
curl -X POST http://localhost:5000/webhook \
|
|
1207
|
+
-H "Content-Type: application/json" \
|
|
1208
|
+
-d '{"content": "Hello"}'
|
|
1209
|
+
|
|
1210
|
+
# Response: 402 with payment challenge
|
|
1211
|
+
# SDK automatically handles payment and retries
|
|
1212
|
+
```
|
|
1213
|
+
|
|
978
1214
|
### MQTT Mode Issues
|
|
979
1215
|
|
|
980
1216
|
**Connection Refused**
|
|
@@ -1030,8 +1266,9 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
1030
1266
|
- [x] Core agent communication and discovery
|
|
1031
1267
|
- [x] End-to-end encryption
|
|
1032
1268
|
- [x] LangChain integration
|
|
1033
|
-
- [x] x402 micropayment support
|
|
1034
|
-
- [x] HTTP Webhook communication mode
|
|
1269
|
+
- [x] x402 micropayment support for HTTP APIs
|
|
1270
|
+
- [x] HTTP Webhook communication mode (async/sync)
|
|
1271
|
+
- [x] x402 payment protection for webhook endpoints
|
|
1035
1272
|
- [ ] WebSocket support for real-time bidirectional communication
|
|
1036
1273
|
- [ ] Support for additional LLM providers (Anthropic, Cohere, etc.)
|
|
1037
1274
|
- [ ] Web dashboard for agent monitoring and payment tracking
|
|
@@ -1041,7 +1278,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
1041
1278
|
- [ ] Enhanced security features (rate limiting, access control)
|
|
1042
1279
|
- [ ] Performance optimizations for high-throughput scenarios
|
|
1043
1280
|
- [ ] x402 payment analytics and budgeting tools
|
|
1044
|
-
- [ ] Webhook authentication and rate limiting
|
|
1281
|
+
- [ ] Webhook authentication and advanced rate limiting
|
|
1045
1282
|
|
|
1046
1283
|
---
|
|
1047
1284
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
zyndai_agent/__init__.py,sha256=1lPLApEVPm3irskK-haMl8-7RN42HVdcXamamFwne8Q,784
|
|
2
|
+
zyndai_agent/agent.py,sha256=wLW7Lp_UKAJ7d8hp5xhQ198X_WqJXCDc3T82LYfbhHQ,7445
|
|
3
|
+
zyndai_agent/communication.py,sha256=kMHvlSoj5aL3pfVxfiQImQQDl5VFC1zXUxX-_PwcFrM,21118
|
|
4
|
+
zyndai_agent/config_manager.py,sha256=9Pl6sEXBjqqKhLRNbhMd5X6HmrSayfvgrPv93zpMZh0,5375
|
|
5
|
+
zyndai_agent/identity.py,sha256=9W9iDcrAg07jxE4llrubW1poYBTVtONddyDULGUSnV8,3906
|
|
6
|
+
zyndai_agent/message.py,sha256=ZmFaGoFJVRWZZsxLAx2FLLUdFjfAos9q-tqFZ2mZEOY,4030
|
|
7
|
+
zyndai_agent/payment.py,sha256=Yxnm8rbSB0B2t78jJwGobtcpRbQlM3lSLpUljohhDgc,6238
|
|
8
|
+
zyndai_agent/search.py,sha256=K_ZJ7FuYs6EAlKRHutXTzgVjpUoSx6yHu4A90Td6xlI,6052
|
|
9
|
+
zyndai_agent/utils.py,sha256=YN1EXGawaUPiPRyPszYvZ7lwTgimmca2DQeW_8nFjRo,16634
|
|
10
|
+
zyndai_agent/webhook_communication.py,sha256=uaZryxiXYor_AT0Nh99uONt_tArkEVkJIXVE0yHV3M0,17235
|
|
11
|
+
zyndai_agent-0.2.2.dist-info/METADATA,sha256=7wALUyFM7Lkd5c3KRqw7QkLJ6Uqev_uHhbSXE-lpHwY,47115
|
|
12
|
+
zyndai_agent-0.2.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
13
|
+
zyndai_agent-0.2.2.dist-info/top_level.txt,sha256=6jE9hyvpa18fstxa4omi9X2c97rawKydn6NwMwVSql4,13
|
|
14
|
+
zyndai_agent-0.2.2.dist-info/RECORD,,
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
zyndai_agent/__init__.py,sha256=HQIL1JElpM14oOWl_7LKQeSxbDSeFMnt7goBFpI7D7I,709
|
|
2
|
-
zyndai_agent/agent.py,sha256=bko2GuGJ4Cd8Ra9IaFncs5j2-OgEcvlR5KW-4sWk1rQ,6262
|
|
3
|
-
zyndai_agent/communication.py,sha256=kMHvlSoj5aL3pfVxfiQImQQDl5VFC1zXUxX-_PwcFrM,21118
|
|
4
|
-
zyndai_agent/identity.py,sha256=9W9iDcrAg07jxE4llrubW1poYBTVtONddyDULGUSnV8,3906
|
|
5
|
-
zyndai_agent/message.py,sha256=nXpKboqAyv-V2bDbgyZ84NerZhGLjYt-lAeM7ndFLTs,3974
|
|
6
|
-
zyndai_agent/payment.py,sha256=Yxnm8rbSB0B2t78jJwGobtcpRbQlM3lSLpUljohhDgc,6238
|
|
7
|
-
zyndai_agent/search.py,sha256=cSLoD4NCXYGo2YiGYE7xYrJr90c_6WjEjoWPQBgu78g,2129
|
|
8
|
-
zyndai_agent/utils.py,sha256=YN1EXGawaUPiPRyPszYvZ7lwTgimmca2DQeW_8nFjRo,16634
|
|
9
|
-
zyndai_agent/webhook_communication.py,sha256=UgyXlNYqooGVoeWqn3uLke8G9vqKBqE1fcFpe1C_mpw,16912
|
|
10
|
-
zyndai_agent-0.2.1.dist-info/METADATA,sha256=FDNvaFcQSzma2Vm8dbWr7d7ERrXacOViaI_d8d7HHMc,37007
|
|
11
|
-
zyndai_agent-0.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
-
zyndai_agent-0.2.1.dist-info/top_level.txt,sha256=6jE9hyvpa18fstxa4omi9X2c97rawKydn6NwMwVSql4,13
|
|
13
|
-
zyndai_agent-0.2.1.dist-info/RECORD,,
|
|
File without changes
|