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 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
- pay_to_address: Optional[str] = None
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
- try:
47
- with open(agent_config.identity_credential_path, "r") as f:
48
- self.identity_credential = json.load(f)
49
- except FileNotFoundError:
50
- raise FileNotFoundError(f"Identity credential file not found at {agent_config.identity_credential_path}")
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=agent_config.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=agent_config.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.agent_config.secret_seed,
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
- "X-API-KEY": self.agent_config.api_key,
126
- "Content-Type": "application/json"
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.agent_config.agent_id,
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
- webhookUrl: Optional[str] # New field for webhook communication
21
+ httpWebhookUrl: Optional[str] # Field for webhook communication
20
22
  inboxTopic: Optional[str]
21
- matchScore: int
23
+ capabilities: Optional[dict]
24
+ status: Optional[str]
22
25
  didIdentifier: str
23
- did: dict
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
- def __init__(self, registry_url: str = "http://localhost:3002/sdk/search"):
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
- def search_agents_by_capabilities(self, capabilities: List[str] = [], match_score_gte: float = 0.5, top_k: Optional[int] = None) -> List[AgentSearchResponse]:
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
- Discover all registered agents in the system based on their capabilities.
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
- match_score_gte: Minimum match score for agents to be included in the results.
42
- top_k: Optional parameter to limit the number of results returned or return all.
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
- logger.info("Discovering agents...")
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
- resp = requests.post(f"{self.registry_url}/sdk/search", json={"userProvidedCapabilities": capabilities})
49
- if resp.status_code == 201:
50
- agents = resp.json()
51
- logger.info(f"Discovered {len(agents)} agents.")
158
+ Returns:
159
+ List of matching agents
160
+ """
161
+ return self.search_agents(keyword=keyword, limit=limit, offset=offset)
52
162
 
53
- filtered_agents = [
54
- agent for agent in agents
55
- if agent.get("matchScore", 0) >= match_score_gte
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
- if top_k is not None:
59
- filtered_agents = filtered_agents[:top_k]
167
+ Args:
168
+ agent_id: The unique identifier of the agent
60
169
 
61
- return filtered_agents
62
- else:
63
- logger.error(f"Failed to discover agents: {resp.status_code} - {resp.text}")
64
- return []
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
- # Update webhook URL if not manually configured
237
+ # Auto-form webhook URL from host and port
238
238
  if self.webhook_url is None:
239
- # Use localhost for local development, can be overridden
240
- self.webhook_url = f"http://localhost:{port}/webhook"
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 JSON and send directly
305
- json_payload = message.to_json()
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 plain JSON
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 webhookUrl
460
+ agent: Agent search response containing httpWebhookUrl
459
461
  """
460
- if "webhookUrl" not in agent:
461
- raise ValueError("Agent does not have webhookUrl. Cannot connect via webhook.")
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 = agent["webhookUrl"]
467
+ self.target_webhook_url = webhook_url
464
468
  self.is_agent_connected = True
465
469
 
466
- logger.info(f"Connected to agent {agent['didIdentifier']} at {self.target_webhook_url}")
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.1
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
- **Note**:
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 (MQTT) │ │
844
- │ │ │ │
845
- │ │ - End-to-End Encryption (ECIES) │ │
846
- │ │ - Message Routing │ │
847
- │ │ - Topic Management │ │
848
- │ │ - History Tracking │ │
849
- └──────────────────────────────────────────┘
850
-
851
- ┌──────────────────────────────────────────┐
852
- x402 Payment Processor │ │
853
- │ │ │ │
854
- │ │ - Payment Challenge Handling
855
- │ │ - Signature Generation
856
- │ │ - Automatic Retry Logic
857
- │ │ - Multi-Method Support (GET/POST/etc)
858
- └──────────────────────────────────────────┘
859
-
860
- ┌──────────────────────────────────────────┐
861
- LangChain Integration │ │
862
- │ │ │ │
863
- │ │ - Agent Executor Support
864
- │ │ - Custom Tools
865
- │ │ - Memory Management
866
- └──────────────────────────────────────────┘
867
- └─────────────────────────────────────────────────────────┘
868
- ▼ ▼
869
- ┌──────────────┐ ┌──────────────┐
870
- Registry │ │ MQTT Broker
871
- Service │ │
872
- └──────────────┘ └──────────────┘
873
-
874
- ┌──────────────┐
875
- x402 Enabled
876
- Services
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,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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,,