docs: add comprehensive contract testing, monitoring, and analytics workflow steps
All checks were successful
API Endpoint Tests / test-api-endpoints (push) Successful in 37s
Documentation Validation / validate-docs (push) Successful in 11s
Integration Tests / test-service-integration (push) Successful in 50s
Python Tests / test-python (push) Successful in 58s
Security Scanning / security-scan (push) Successful in 1m1s
All checks were successful
API Endpoint Tests / test-api-endpoints (push) Successful in 37s
Documentation Validation / validate-docs (push) Successful in 11s
Integration Tests / test-service-integration (push) Successful in 50s
Python Tests / test-python (push) Successful in 58s
Security Scanning / security-scan (push) Successful in 1m1s
📋 Workflow Enhancement:
• Add cross-node consensus testing with debugging reports (step 6)
• Add smart contract testing and service integration (step 7)
• Add enhanced contract and service testing with API structure validation (step 8)
• Add service health monitoring with quick, continuous, and alert modes (step 9)
• Add contract deployment and service integration testing (step 10)
• Add contract security and vulnerability testing with reports (step 11)
• Add
This commit is contained in:
@@ -0,0 +1,519 @@
|
||||
"""
|
||||
AITBC Agent Messaging Contract Implementation
|
||||
|
||||
This module implements on-chain messaging functionality for agents,
|
||||
enabling forum-like communication between autonomous agents.
|
||||
"""
|
||||
|
||||
from typing import Dict, List, Optional, Any
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timedelta
|
||||
from enum import Enum
|
||||
import json
|
||||
import hashlib
|
||||
from eth_account import Account
|
||||
from eth_utils import to_checksum_address
|
||||
|
||||
class MessageType(Enum):
|
||||
"""Types of messages agents can send"""
|
||||
POST = "post"
|
||||
REPLY = "reply"
|
||||
ANNOUNCEMENT = "announcement"
|
||||
QUESTION = "question"
|
||||
ANSWER = "answer"
|
||||
MODERATION = "moderation"
|
||||
|
||||
class MessageStatus(Enum):
|
||||
"""Status of messages in the forum"""
|
||||
ACTIVE = "active"
|
||||
HIDDEN = "hidden"
|
||||
DELETED = "deleted"
|
||||
PINNED = "pinned"
|
||||
|
||||
@dataclass
|
||||
class Message:
|
||||
"""Represents a message in the agent forum"""
|
||||
message_id: str
|
||||
agent_id: str
|
||||
agent_address: str
|
||||
topic: str
|
||||
content: str
|
||||
message_type: MessageType
|
||||
timestamp: datetime
|
||||
parent_message_id: Optional[str] = None
|
||||
reply_count: int = 0
|
||||
upvotes: int = 0
|
||||
downvotes: int = 0
|
||||
status: MessageStatus = MessageStatus.ACTIVE
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@dataclass
|
||||
class Topic:
|
||||
"""Represents a forum topic"""
|
||||
topic_id: str
|
||||
title: str
|
||||
description: str
|
||||
creator_agent_id: str
|
||||
created_at: datetime
|
||||
message_count: int = 0
|
||||
last_activity: datetime = field(default_factory=datetime.now)
|
||||
tags: List[str] = field(default_factory=list)
|
||||
is_pinned: bool = False
|
||||
is_locked: bool = False
|
||||
|
||||
@dataclass
|
||||
class AgentReputation:
|
||||
"""Reputation system for agents"""
|
||||
agent_id: str
|
||||
message_count: int = 0
|
||||
upvotes_received: int = 0
|
||||
downvotes_received: int = 0
|
||||
reputation_score: float = 0.0
|
||||
trust_level: int = 1 # 1-5 trust levels
|
||||
is_moderator: bool = False
|
||||
is_banned: bool = False
|
||||
ban_reason: Optional[str] = None
|
||||
ban_expires: Optional[datetime] = None
|
||||
|
||||
class AgentMessagingContract:
|
||||
"""Main contract for agent messaging functionality"""
|
||||
|
||||
def __init__(self):
|
||||
self.messages: Dict[str, Message] = {}
|
||||
self.topics: Dict[str, Topic] = {}
|
||||
self.agent_reputations: Dict[str, AgentReputation] = {}
|
||||
self.moderation_log: List[Dict[str, Any]] = []
|
||||
|
||||
def create_topic(self, agent_id: str, agent_address: str, title: str,
|
||||
description: str, tags: List[str] = None) -> Dict[str, Any]:
|
||||
"""Create a new forum topic"""
|
||||
|
||||
# Check if agent is banned
|
||||
if self._is_agent_banned(agent_id):
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Agent is banned from posting",
|
||||
"error_code": "AGENT_BANNED"
|
||||
}
|
||||
|
||||
# Generate topic ID
|
||||
topic_id = f"topic_{hashlib.sha256(f'{agent_id}_{title}_{datetime.now()}'.encode()).hexdigest()[:16]}"
|
||||
|
||||
# Create topic
|
||||
topic = Topic(
|
||||
topic_id=topic_id,
|
||||
title=title,
|
||||
description=description,
|
||||
creator_agent_id=agent_id,
|
||||
created_at=datetime.now(),
|
||||
tags=tags or []
|
||||
)
|
||||
|
||||
self.topics[topic_id] = topic
|
||||
|
||||
# Update agent reputation
|
||||
self._update_agent_reputation(agent_id, message_count=1)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"topic_id": topic_id,
|
||||
"topic": self._topic_to_dict(topic)
|
||||
}
|
||||
|
||||
def post_message(self, agent_id: str, agent_address: str, topic_id: str,
|
||||
content: str, message_type: str = "post",
|
||||
parent_message_id: str = None) -> Dict[str, Any]:
|
||||
"""Post a message to a forum topic"""
|
||||
|
||||
# Validate inputs
|
||||
if not self._validate_agent(agent_id, agent_address):
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Invalid agent credentials",
|
||||
"error_code": "INVALID_AGENT"
|
||||
}
|
||||
|
||||
if self._is_agent_banned(agent_id):
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Agent is banned from posting",
|
||||
"error_code": "AGENT_BANNED"
|
||||
}
|
||||
|
||||
if topic_id not in self.topics:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Topic not found",
|
||||
"error_code": "TOPIC_NOT_FOUND"
|
||||
}
|
||||
|
||||
if self.topics[topic_id].is_locked:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Topic is locked",
|
||||
"error_code": "TOPIC_LOCKED"
|
||||
}
|
||||
|
||||
# Validate message type
|
||||
try:
|
||||
msg_type = MessageType(message_type)
|
||||
except ValueError:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Invalid message type",
|
||||
"error_code": "INVALID_MESSAGE_TYPE"
|
||||
}
|
||||
|
||||
# Generate message ID
|
||||
message_id = f"msg_{hashlib.sha256(f'{agent_id}_{topic_id}_{content}_{datetime.now()}'.encode()).hexdigest()[:16]}"
|
||||
|
||||
# Create message
|
||||
message = Message(
|
||||
message_id=message_id,
|
||||
agent_id=agent_id,
|
||||
agent_address=agent_address,
|
||||
topic=topic_id,
|
||||
content=content,
|
||||
message_type=msg_type,
|
||||
timestamp=datetime.now(),
|
||||
parent_message_id=parent_message_id
|
||||
)
|
||||
|
||||
self.messages[message_id] = message
|
||||
|
||||
# Update topic
|
||||
self.topics[topic_id].message_count += 1
|
||||
self.topics[topic_id].last_activity = datetime.now()
|
||||
|
||||
# Update parent message if this is a reply
|
||||
if parent_message_id and parent_message_id in self.messages:
|
||||
self.messages[parent_message_id].reply_count += 1
|
||||
|
||||
# Update agent reputation
|
||||
self._update_agent_reputation(agent_id, message_count=1)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message_id": message_id,
|
||||
"message": self._message_to_dict(message)
|
||||
}
|
||||
|
||||
def get_messages(self, topic_id: str, limit: int = 50, offset: int = 0,
|
||||
sort_by: str = "timestamp") -> Dict[str, Any]:
|
||||
"""Get messages from a topic"""
|
||||
|
||||
if topic_id not in self.topics:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Topic not found",
|
||||
"error_code": "TOPIC_NOT_FOUND"
|
||||
}
|
||||
|
||||
# Get all messages for this topic
|
||||
topic_messages = [
|
||||
msg for msg in self.messages.values()
|
||||
if msg.topic == topic_id and msg.status == MessageStatus.ACTIVE
|
||||
]
|
||||
|
||||
# Sort messages
|
||||
if sort_by == "timestamp":
|
||||
topic_messages.sort(key=lambda x: x.timestamp, reverse=True)
|
||||
elif sort_by == "upvotes":
|
||||
topic_messages.sort(key=lambda x: x.upvotes, reverse=True)
|
||||
elif sort_by == "replies":
|
||||
topic_messages.sort(key=lambda x: x.reply_count, reverse=True)
|
||||
|
||||
# Apply pagination
|
||||
total_messages = len(topic_messages)
|
||||
paginated_messages = topic_messages[offset:offset + limit]
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"messages": [self._message_to_dict(msg) for msg in paginated_messages],
|
||||
"total_messages": total_messages,
|
||||
"topic": self._topic_to_dict(self.topics[topic_id])
|
||||
}
|
||||
|
||||
def get_topics(self, limit: int = 50, offset: int = 0,
|
||||
sort_by: str = "last_activity") -> Dict[str, Any]:
|
||||
"""Get list of forum topics"""
|
||||
|
||||
# Sort topics
|
||||
topic_list = list(self.topics.values())
|
||||
|
||||
if sort_by == "last_activity":
|
||||
topic_list.sort(key=lambda x: x.last_activity, reverse=True)
|
||||
elif sort_by == "created_at":
|
||||
topic_list.sort(key=lambda x: x.created_at, reverse=True)
|
||||
elif sort_by == "message_count":
|
||||
topic_list.sort(key=lambda x: x.message_count, reverse=True)
|
||||
|
||||
# Apply pagination
|
||||
total_topics = len(topic_list)
|
||||
paginated_topics = topic_list[offset:offset + limit]
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"topics": [self._topic_to_dict(topic) for topic in paginated_topics],
|
||||
"total_topics": total_topics
|
||||
}
|
||||
|
||||
def vote_message(self, agent_id: str, agent_address: str, message_id: str,
|
||||
vote_type: str) -> Dict[str, Any]:
|
||||
"""Vote on a message (upvote/downvote)"""
|
||||
|
||||
# Validate inputs
|
||||
if not self._validate_agent(agent_id, agent_address):
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Invalid agent credentials",
|
||||
"error_code": "INVALID_AGENT"
|
||||
}
|
||||
|
||||
if message_id not in self.messages:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Message not found",
|
||||
"error_code": "MESSAGE_NOT_FOUND"
|
||||
}
|
||||
|
||||
if vote_type not in ["upvote", "downvote"]:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Invalid vote type",
|
||||
"error_code": "INVALID_VOTE_TYPE"
|
||||
}
|
||||
|
||||
message = self.messages[message_id]
|
||||
|
||||
# Update vote counts
|
||||
if vote_type == "upvote":
|
||||
message.upvotes += 1
|
||||
else:
|
||||
message.downvotes += 1
|
||||
|
||||
# Update message author reputation
|
||||
self._update_agent_reputation(
|
||||
message.agent_id,
|
||||
upvotes_received=message.upvotes,
|
||||
downvotes_received=message.downvotes
|
||||
)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message_id": message_id,
|
||||
"upvotes": message.upvotes,
|
||||
"downvotes": message.downvotes
|
||||
}
|
||||
|
||||
def moderate_message(self, moderator_agent_id: str, moderator_address: str,
|
||||
message_id: str, action: str, reason: str = "") -> Dict[str, Any]:
|
||||
"""Moderate a message (hide, delete, pin)"""
|
||||
|
||||
# Validate moderator
|
||||
if not self._is_moderator(moderator_agent_id):
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Insufficient permissions",
|
||||
"error_code": "INSUFFICIENT_PERMISSIONS"
|
||||
}
|
||||
|
||||
if message_id not in self.messages:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Message not found",
|
||||
"error_code": "MESSAGE_NOT_FOUND"
|
||||
}
|
||||
|
||||
message = self.messages[message_id]
|
||||
|
||||
# Apply moderation action
|
||||
if action == "hide":
|
||||
message.status = MessageStatus.HIDDEN
|
||||
elif action == "delete":
|
||||
message.status = MessageStatus.DELETED
|
||||
elif action == "pin":
|
||||
message.status = MessageStatus.PINNED
|
||||
elif action == "unpin":
|
||||
message.status = MessageStatus.ACTIVE
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Invalid moderation action",
|
||||
"error_code": "INVALID_ACTION"
|
||||
}
|
||||
|
||||
# Log moderation action
|
||||
self.moderation_log.append({
|
||||
"timestamp": datetime.now(),
|
||||
"moderator_agent_id": moderator_agent_id,
|
||||
"message_id": message_id,
|
||||
"action": action,
|
||||
"reason": reason
|
||||
})
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message_id": message_id,
|
||||
"status": message.status.value
|
||||
}
|
||||
|
||||
def get_agent_reputation(self, agent_id: str) -> Dict[str, Any]:
|
||||
"""Get an agent's reputation information"""
|
||||
|
||||
if agent_id not in self.agent_reputations:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Agent not found",
|
||||
"error_code": "AGENT_NOT_FOUND"
|
||||
}
|
||||
|
||||
reputation = self.agent_reputations[agent_id]
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"agent_id": agent_id,
|
||||
"reputation": self._reputation_to_dict(reputation)
|
||||
}
|
||||
|
||||
def search_messages(self, query: str, limit: int = 50) -> Dict[str, Any]:
|
||||
"""Search messages by content"""
|
||||
|
||||
# Simple text search (in production, use proper search engine)
|
||||
query_lower = query.lower()
|
||||
matching_messages = []
|
||||
|
||||
for message in self.messages.values():
|
||||
if (message.status == MessageStatus.ACTIVE and
|
||||
query_lower in message.content.lower()):
|
||||
matching_messages.append(message)
|
||||
|
||||
# Sort by timestamp (most recent first)
|
||||
matching_messages.sort(key=lambda x: x.timestamp, reverse=True)
|
||||
|
||||
# Limit results
|
||||
limited_messages = matching_messages[:limit]
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"query": query,
|
||||
"messages": [self._message_to_dict(msg) for msg in limited_messages],
|
||||
"total_matches": len(matching_messages)
|
||||
}
|
||||
|
||||
def _validate_agent(self, agent_id: str, agent_address: str) -> bool:
|
||||
"""Validate agent credentials"""
|
||||
# In a real implementation, this would verify the agent's signature
|
||||
# For now, we'll do basic validation
|
||||
return bool(agent_id and agent_address)
|
||||
|
||||
def _is_agent_banned(self, agent_id: str) -> bool:
|
||||
"""Check if an agent is banned"""
|
||||
if agent_id not in self.agent_reputations:
|
||||
return False
|
||||
|
||||
reputation = self.agent_reputations[agent_id]
|
||||
|
||||
if reputation.is_banned:
|
||||
# Check if ban has expired
|
||||
if reputation.ban_expires and datetime.now() > reputation.ban_expires:
|
||||
reputation.is_banned = False
|
||||
reputation.ban_expires = None
|
||||
reputation.ban_reason = None
|
||||
return False
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _is_moderator(self, agent_id: str) -> bool:
|
||||
"""Check if an agent is a moderator"""
|
||||
if agent_id not in self.agent_reputations:
|
||||
return False
|
||||
|
||||
return self.agent_reputations[agent_id].is_moderator
|
||||
|
||||
def _update_agent_reputation(self, agent_id: str, message_count: int = 0,
|
||||
upvotes_received: int = 0, downvotes_received: int = 0):
|
||||
"""Update agent reputation"""
|
||||
|
||||
if agent_id not in self.agent_reputations:
|
||||
self.agent_reputations[agent_id] = AgentReputation(agent_id=agent_id)
|
||||
|
||||
reputation = self.agent_reputations[agent_id]
|
||||
|
||||
if message_count > 0:
|
||||
reputation.message_count += message_count
|
||||
|
||||
if upvotes_received > 0:
|
||||
reputation.upvotes_received += upvotes_received
|
||||
|
||||
if downvotes_received > 0:
|
||||
reputation.downvotes_received += downvotes_received
|
||||
|
||||
# Calculate reputation score
|
||||
total_votes = reputation.upvotes_received + reputation.downvotes_received
|
||||
if total_votes > 0:
|
||||
reputation.reputation_score = (reputation.upvotes_received - reputation.downvotes_received) / total_votes
|
||||
|
||||
# Update trust level based on reputation score
|
||||
if reputation.reputation_score >= 0.8:
|
||||
reputation.trust_level = 5
|
||||
elif reputation.reputation_score >= 0.6:
|
||||
reputation.trust_level = 4
|
||||
elif reputation.reputation_score >= 0.4:
|
||||
reputation.trust_level = 3
|
||||
elif reputation.reputation_score >= 0.2:
|
||||
reputation.trust_level = 2
|
||||
else:
|
||||
reputation.trust_level = 1
|
||||
|
||||
def _message_to_dict(self, message: Message) -> Dict[str, Any]:
|
||||
"""Convert message to dictionary"""
|
||||
return {
|
||||
"message_id": message.message_id,
|
||||
"agent_id": message.agent_id,
|
||||
"agent_address": message.agent_address,
|
||||
"topic": message.topic,
|
||||
"content": message.content,
|
||||
"message_type": message.message_type.value,
|
||||
"timestamp": message.timestamp.isoformat(),
|
||||
"parent_message_id": message.parent_message_id,
|
||||
"reply_count": message.reply_count,
|
||||
"upvotes": message.upvotes,
|
||||
"downvotes": message.downvotes,
|
||||
"status": message.status.value,
|
||||
"metadata": message.metadata
|
||||
}
|
||||
|
||||
def _topic_to_dict(self, topic: Topic) -> Dict[str, Any]:
|
||||
"""Convert topic to dictionary"""
|
||||
return {
|
||||
"topic_id": topic.topic_id,
|
||||
"title": topic.title,
|
||||
"description": topic.description,
|
||||
"creator_agent_id": topic.creator_agent_id,
|
||||
"created_at": topic.created_at.isoformat(),
|
||||
"message_count": topic.message_count,
|
||||
"last_activity": topic.last_activity.isoformat(),
|
||||
"tags": topic.tags,
|
||||
"is_pinned": topic.is_pinned,
|
||||
"is_locked": topic.is_locked
|
||||
}
|
||||
|
||||
def _reputation_to_dict(self, reputation: AgentReputation) -> Dict[str, Any]:
|
||||
"""Convert reputation to dictionary"""
|
||||
return {
|
||||
"agent_id": reputation.agent_id,
|
||||
"message_count": reputation.message_count,
|
||||
"upvotes_received": reputation.upvotes_received,
|
||||
"downvotes_received": reputation.downvotes_received,
|
||||
"reputation_score": reputation.reputation_score,
|
||||
"trust_level": reputation.trust_level,
|
||||
"is_moderator": reputation.is_moderator,
|
||||
"is_banned": reputation.is_banned,
|
||||
"ban_reason": reputation.ban_reason,
|
||||
"ban_expires": reputation.ban_expires.isoformat() if reputation.ban_expires else None
|
||||
}
|
||||
|
||||
# Global contract instance
|
||||
messaging_contract = AgentMessagingContract()
|
||||
11
apps/blockchain-node/src/aitbc_chain/rpc/contract_service.py
Normal file
11
apps/blockchain-node/src/aitbc_chain/rpc/contract_service.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Contract Service Module"""
|
||||
from typing import Dict, Any
|
||||
import hashlib
|
||||
import time
|
||||
|
||||
class ContractService:
|
||||
@staticmethod
|
||||
def list_contracts() -> Dict[str, Any]:
|
||||
return {"contracts": [{"address": "0xguardian_001", "name": "Guardian Contract", "status": "deployed", "functions": ["storeValue", "getValue", "setGuardian"]}], "total": 1}
|
||||
|
||||
contract_service = ContractService()
|
||||
File diff suppressed because it is too large
Load Diff
754
apps/coordinator-api/src/app/agent_identity/sdk/communication.py
Normal file
754
apps/coordinator-api/src/app/agent_identity/sdk/communication.py
Normal file
@@ -0,0 +1,754 @@
|
||||
"""
|
||||
AITBC Agent Communication SDK Extension
|
||||
|
||||
This module extends the Agent Identity SDK with communication methods
|
||||
for forum-like agent interactions using the blockchain messaging contract.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional, Any, Union
|
||||
from dataclasses import dataclass
|
||||
import hashlib
|
||||
import logging
|
||||
|
||||
from .client import AgentIdentityClient
|
||||
from .models import AgentIdentity, AgentWallet
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@dataclass
|
||||
class ForumTopic:
|
||||
"""Forum topic data structure"""
|
||||
topic_id: str
|
||||
title: str
|
||||
description: str
|
||||
creator_agent_id: str
|
||||
created_at: datetime
|
||||
message_count: int
|
||||
last_activity: datetime
|
||||
tags: List[str]
|
||||
is_pinned: bool
|
||||
is_locked: bool
|
||||
|
||||
@dataclass
|
||||
class ForumMessage:
|
||||
"""Forum message data structure"""
|
||||
message_id: str
|
||||
agent_id: str
|
||||
agent_address: str
|
||||
topic: str
|
||||
content: str
|
||||
message_type: str
|
||||
timestamp: datetime
|
||||
parent_message_id: Optional[str]
|
||||
reply_count: int
|
||||
upvotes: int
|
||||
downvotes: int
|
||||
status: str
|
||||
metadata: Dict[str, Any]
|
||||
|
||||
@dataclass
|
||||
class AgentReputation:
|
||||
"""Agent reputation data structure"""
|
||||
agent_id: str
|
||||
message_count: int
|
||||
upvotes_received: int
|
||||
downvotes_received: int
|
||||
reputation_score: float
|
||||
trust_level: int
|
||||
is_moderator: bool
|
||||
is_banned: bool
|
||||
ban_reason: Optional[str]
|
||||
ban_expires: Optional[datetime]
|
||||
|
||||
class AgentCommunicationClient:
|
||||
"""Extended client for agent communication functionality"""
|
||||
|
||||
def __init__(self, base_url: str, agent_id: str, private_key: str = None):
|
||||
"""
|
||||
Initialize the communication client
|
||||
|
||||
Args:
|
||||
base_url: Base URL for the coordinator API
|
||||
agent_id: Agent identifier
|
||||
private_key: Agent's private key for signing messages
|
||||
"""
|
||||
self.base_url = base_url
|
||||
self.agent_id = agent_id
|
||||
self.private_key = private_key
|
||||
self.identity_client = AgentIdentityClient(base_url, agent_id, private_key)
|
||||
|
||||
async def create_forum_topic(self, title: str, description: str,
|
||||
tags: List[str] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Create a new forum topic
|
||||
|
||||
Args:
|
||||
title: Topic title
|
||||
description: Topic description
|
||||
tags: Optional list of tags
|
||||
|
||||
Returns:
|
||||
Topic creation result
|
||||
"""
|
||||
try:
|
||||
# Verify agent identity
|
||||
identity = await self.identity_client.get_identity()
|
||||
if not identity:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Agent identity not found",
|
||||
"error_code": "IDENTITY_NOT_FOUND"
|
||||
}
|
||||
|
||||
# Get agent address
|
||||
agent_address = identity.wallets[0].address if identity.wallets else None
|
||||
if not agent_address:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "No wallet found for agent",
|
||||
"error_code": "NO_WALLET_FOUND"
|
||||
}
|
||||
|
||||
# Create topic via blockchain contract
|
||||
topic_data = {
|
||||
"agent_id": self.agent_id,
|
||||
"agent_address": agent_address,
|
||||
"title": title,
|
||||
"description": description,
|
||||
"tags": tags or []
|
||||
}
|
||||
|
||||
# This would call the blockchain contract
|
||||
result = await self._call_messaging_contract("create_topic", topic_data)
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating forum topic: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error_code": "TOPIC_CREATION_FAILED"
|
||||
}
|
||||
|
||||
async def post_message(self, topic_id: str, content: str,
|
||||
message_type: str = "post",
|
||||
parent_message_id: str = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Post a message to a forum topic
|
||||
|
||||
Args:
|
||||
topic_id: Target topic ID
|
||||
content: Message content
|
||||
message_type: Type of message (post, reply, question, etc.)
|
||||
parent_message_id: Parent message ID for replies
|
||||
|
||||
Returns:
|
||||
Message posting result
|
||||
"""
|
||||
try:
|
||||
# Verify agent identity
|
||||
identity = await self.identity_client.get_identity()
|
||||
if not identity:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Agent identity not found",
|
||||
"error_code": "IDENTITY_NOT_FOUND"
|
||||
}
|
||||
|
||||
# Get agent address
|
||||
agent_address = identity.wallets[0].address if identity.wallets else None
|
||||
if not agent_address:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "No wallet found for agent",
|
||||
"error_code": "NO_WALLET_FOUND"
|
||||
}
|
||||
|
||||
# Post message via blockchain contract
|
||||
message_data = {
|
||||
"agent_id": self.agent_id,
|
||||
"agent_address": agent_address,
|
||||
"topic_id": topic_id,
|
||||
"content": content,
|
||||
"message_type": message_type,
|
||||
"parent_message_id": parent_message_id
|
||||
}
|
||||
|
||||
result = await self._call_messaging_contract("post_message", message_data)
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error posting message: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error_code": "MESSAGE_POSTING_FAILED"
|
||||
}
|
||||
|
||||
async def get_topic_messages(self, topic_id: str, limit: int = 50,
|
||||
offset: int = 0, sort_by: str = "timestamp") -> Dict[str, Any]:
|
||||
"""
|
||||
Get messages from a forum topic
|
||||
|
||||
Args:
|
||||
topic_id: Topic ID
|
||||
limit: Maximum number of messages to return
|
||||
offset: Offset for pagination
|
||||
sort_by: Sort method (timestamp, upvotes, replies)
|
||||
|
||||
Returns:
|
||||
Messages and topic information
|
||||
"""
|
||||
try:
|
||||
params = {
|
||||
"topic_id": topic_id,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
"sort_by": sort_by
|
||||
}
|
||||
|
||||
result = await self._call_messaging_contract("get_messages", params)
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting topic messages: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error_code": "GET_MESSAGES_FAILED"
|
||||
}
|
||||
|
||||
async def get_forum_topics(self, limit: int = 50, offset: int = 0,
|
||||
sort_by: str = "last_activity") -> Dict[str, Any]:
|
||||
"""
|
||||
Get list of forum topics
|
||||
|
||||
Args:
|
||||
limit: Maximum number of topics to return
|
||||
offset: Offset for pagination
|
||||
sort_by: Sort method (last_activity, created_at, message_count)
|
||||
|
||||
Returns:
|
||||
List of topics
|
||||
"""
|
||||
try:
|
||||
params = {
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
"sort_by": sort_by
|
||||
}
|
||||
|
||||
result = await self._call_messaging_contract("get_topics", params)
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting forum topics: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error_code": "GET_TOPICS_FAILED"
|
||||
}
|
||||
|
||||
async def vote_message(self, message_id: str, vote_type: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Vote on a message (upvote/downvote)
|
||||
|
||||
Args:
|
||||
message_id: Message ID to vote on
|
||||
vote_type: Type of vote ("upvote" or "downvote")
|
||||
|
||||
Returns:
|
||||
Vote result
|
||||
"""
|
||||
try:
|
||||
# Verify agent identity
|
||||
identity = await self.identity_client.get_identity()
|
||||
if not identity:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Agent identity not found",
|
||||
"error_code": "IDENTITY_NOT_FOUND"
|
||||
}
|
||||
|
||||
# Get agent address
|
||||
agent_address = identity.wallets[0].address if identity.wallets else None
|
||||
if not agent_address:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "No wallet found for agent",
|
||||
"error_code": "NO_WALLET_FOUND"
|
||||
}
|
||||
|
||||
vote_data = {
|
||||
"agent_id": self.agent_id,
|
||||
"agent_address": agent_address,
|
||||
"message_id": message_id,
|
||||
"vote_type": vote_type
|
||||
}
|
||||
|
||||
result = await self._call_messaging_contract("vote_message", vote_data)
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error voting on message: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error_code": "VOTE_FAILED"
|
||||
}
|
||||
|
||||
async def reply_to_message(self, message_id: str, content: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Reply to a message
|
||||
|
||||
Args:
|
||||
message_id: Parent message ID
|
||||
content: Reply content
|
||||
|
||||
Returns:
|
||||
Reply posting result
|
||||
"""
|
||||
try:
|
||||
# Get the original message to find the topic
|
||||
original_message = await self._get_message_details(message_id)
|
||||
if not original_message.get("success"):
|
||||
return original_message
|
||||
|
||||
topic_id = original_message["message"]["topic"]
|
||||
|
||||
# Post as a reply
|
||||
return await self.post_message(
|
||||
topic_id=topic_id,
|
||||
content=content,
|
||||
message_type="reply",
|
||||
parent_message_id=message_id
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error replying to message: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error_code": "REPLY_FAILED"
|
||||
}
|
||||
|
||||
async def search_messages(self, query: str, limit: int = 50) -> Dict[str, Any]:
|
||||
"""
|
||||
Search messages by content
|
||||
|
||||
Args:
|
||||
query: Search query
|
||||
limit: Maximum number of results
|
||||
|
||||
Returns:
|
||||
Search results
|
||||
"""
|
||||
try:
|
||||
params = {
|
||||
"query": query,
|
||||
"limit": limit
|
||||
}
|
||||
|
||||
result = await self._call_messaging_contract("search_messages", params)
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error searching messages: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error_code": "SEARCH_FAILED"
|
||||
}
|
||||
|
||||
async def get_agent_reputation(self, agent_id: str = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Get agent reputation information
|
||||
|
||||
Args:
|
||||
agent_id: Agent ID (defaults to current agent)
|
||||
|
||||
Returns:
|
||||
Reputation information
|
||||
"""
|
||||
try:
|
||||
target_agent_id = agent_id or self.agent_id
|
||||
|
||||
result = await self._call_messaging_contract("get_agent_reputation", {
|
||||
"agent_id": target_agent_id
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting agent reputation: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error_code": "GET_REPUTATION_FAILED"
|
||||
}
|
||||
|
||||
async def moderate_message(self, message_id: str, action: str,
|
||||
reason: str = "") -> Dict[str, Any]:
|
||||
"""
|
||||
Moderate a message (moderator only)
|
||||
|
||||
Args:
|
||||
message_id: Message ID to moderate
|
||||
action: Action to take (hide, delete, pin, unpin)
|
||||
reason: Reason for moderation
|
||||
|
||||
Returns:
|
||||
Moderation result
|
||||
"""
|
||||
try:
|
||||
# Verify agent is a moderator
|
||||
reputation = await self.get_agent_reputation()
|
||||
if not reputation.get("success"):
|
||||
return reputation
|
||||
|
||||
if not reputation["reputation"].get("is_moderator", False):
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Insufficient permissions",
|
||||
"error_code": "INSUFFICIENT_PERMISSIONS"
|
||||
}
|
||||
|
||||
# Get agent address
|
||||
identity = await self.identity_client.get_identity()
|
||||
agent_address = identity.wallets[0].address if identity.wallets else None
|
||||
|
||||
moderation_data = {
|
||||
"moderator_agent_id": self.agent_id,
|
||||
"moderator_address": agent_address,
|
||||
"message_id": message_id,
|
||||
"action": action,
|
||||
"reason": reason
|
||||
}
|
||||
|
||||
result = await self._call_messaging_contract("moderate_message", moderation_data)
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error moderating message: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error_code": "MODERATION_FAILED"
|
||||
}
|
||||
|
||||
async def create_announcement(self, content: str, topic_id: str = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Create an announcement message
|
||||
|
||||
Args:
|
||||
content: Announcement content
|
||||
topic_id: Optional topic ID (creates new topic if not provided)
|
||||
|
||||
Returns:
|
||||
Announcement creation result
|
||||
"""
|
||||
try:
|
||||
if topic_id:
|
||||
# Post to existing topic
|
||||
return await self.post_message(topic_id, content, "announcement")
|
||||
else:
|
||||
# Create new topic for announcement
|
||||
title = f"Announcement from {self.agent_id}"
|
||||
description = "Official announcement"
|
||||
|
||||
topic_result = await self.create_forum_topic(title, description, ["announcement"])
|
||||
if not topic_result.get("success"):
|
||||
return topic_result
|
||||
|
||||
# Post announcement to new topic
|
||||
return await self.post_message(topic_result["topic_id"], content, "announcement")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating announcement: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error_code": "ANNOUNCEMENT_FAILED"
|
||||
}
|
||||
|
||||
async def ask_question(self, topic_id: str, question: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Ask a question in a forum topic
|
||||
|
||||
Args:
|
||||
topic_id: Topic ID
|
||||
question: Question content
|
||||
|
||||
Returns:
|
||||
Question posting result
|
||||
"""
|
||||
return await self.post_message(topic_id, question, "question")
|
||||
|
||||
async def answer_question(self, message_id: str, answer: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Answer a question
|
||||
|
||||
Args:
|
||||
message_id: Question message ID
|
||||
answer: Answer content
|
||||
|
||||
Returns:
|
||||
Answer posting result
|
||||
"""
|
||||
try:
|
||||
# Get the original question to find the topic
|
||||
original_message = await self._get_message_details(message_id)
|
||||
if not original_message.get("success"):
|
||||
return original_message
|
||||
|
||||
topic_id = original_message["message"]["topic"]
|
||||
|
||||
# Post as an answer
|
||||
return await self.post_message(
|
||||
topic_id=topic_id,
|
||||
content=answer,
|
||||
message_type="answer",
|
||||
parent_message_id=message_id
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error answering question: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error_code": "ANSWER_FAILED"
|
||||
}
|
||||
|
||||
async def _call_messaging_contract(self, method: str, params: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Call the messaging contract method
|
||||
|
||||
Args:
|
||||
method: Contract method name
|
||||
params: Method parameters
|
||||
|
||||
Returns:
|
||||
Contract call result
|
||||
"""
|
||||
# This would make an actual call to the blockchain contract
|
||||
# For now, we'll simulate the call
|
||||
|
||||
try:
|
||||
# In a real implementation, this would:
|
||||
# 1. Sign the transaction
|
||||
# 2. Call the smart contract
|
||||
# 3. Wait for confirmation
|
||||
# 4. Return the result
|
||||
|
||||
# For simulation, we'll return a mock response
|
||||
if method == "create_topic":
|
||||
topic_id = f"topic_{hashlib.sha256(f'{params.get(\"agent_id\")}_{params.get(\"title\")}_{datetime.now()}'.encode()).hexdigest()[:16]}"
|
||||
return {
|
||||
"success": True,
|
||||
"topic_id": topic_id,
|
||||
"topic": {
|
||||
"topic_id": topic_id,
|
||||
"title": params["title"],
|
||||
"description": params["description"],
|
||||
"creator_agent_id": params["agent_id"],
|
||||
"created_at": datetime.now().isoformat(),
|
||||
"message_count": 0,
|
||||
"last_activity": datetime.now().isoformat(),
|
||||
"tags": params.get("tags", []),
|
||||
"is_pinned": False,
|
||||
"is_locked": False
|
||||
}
|
||||
}
|
||||
|
||||
elif method == "post_message":
|
||||
message_id = f"msg_{hashlib.sha256(f'{params.get(\"agent_id\")}_{params.get(\"topic_id\")}_{params.get(\"content\")}_{datetime.now()}'.encode()).hexdigest()[:16]}"
|
||||
return {
|
||||
"success": True,
|
||||
"message_id": message_id,
|
||||
"message": {
|
||||
"message_id": message_id,
|
||||
"agent_id": params["agent_id"],
|
||||
"agent_address": params["agent_address"],
|
||||
"topic": params["topic_id"],
|
||||
"content": params["content"],
|
||||
"message_type": params["message_type"],
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"parent_message_id": params.get("parent_message_id"),
|
||||
"reply_count": 0,
|
||||
"upvotes": 0,
|
||||
"downvotes": 0,
|
||||
"status": "active",
|
||||
"metadata": {}
|
||||
}
|
||||
}
|
||||
|
||||
elif method == "get_messages":
|
||||
return {
|
||||
"success": True,
|
||||
"messages": [],
|
||||
"total_messages": 0,
|
||||
"topic": {
|
||||
"topic_id": params["topic_id"],
|
||||
"title": "Sample Topic",
|
||||
"description": "Sample description"
|
||||
}
|
||||
}
|
||||
|
||||
elif method == "get_topics":
|
||||
return {
|
||||
"success": True,
|
||||
"topics": [],
|
||||
"total_topics": 0
|
||||
}
|
||||
|
||||
elif method == "vote_message":
|
||||
return {
|
||||
"success": True,
|
||||
"message_id": params["message_id"],
|
||||
"upvotes": 1,
|
||||
"downvotes": 0
|
||||
}
|
||||
|
||||
elif method == "search_messages":
|
||||
return {
|
||||
"success": True,
|
||||
"query": params["query"],
|
||||
"messages": [],
|
||||
"total_matches": 0
|
||||
}
|
||||
|
||||
elif method == "get_agent_reputation":
|
||||
return {
|
||||
"success": True,
|
||||
"agent_id": params["agent_id"],
|
||||
"reputation": {
|
||||
"agent_id": params["agent_id"],
|
||||
"message_count": 0,
|
||||
"upvotes_received": 0,
|
||||
"downvotes_received": 0,
|
||||
"reputation_score": 0.0,
|
||||
"trust_level": 1,
|
||||
"is_moderator": False,
|
||||
"is_banned": False,
|
||||
"ban_reason": None,
|
||||
"ban_expires": None
|
||||
}
|
||||
}
|
||||
|
||||
elif method == "moderate_message":
|
||||
return {
|
||||
"success": True,
|
||||
"message_id": params["message_id"],
|
||||
"status": params["action"]
|
||||
}
|
||||
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"Unknown method: {method}",
|
||||
"error_code": "UNKNOWN_METHOD"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error calling messaging contract: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error_code": "CONTRACT_CALL_FAILED"
|
||||
}
|
||||
|
||||
async def _get_message_details(self, message_id: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Get details of a specific message
|
||||
|
||||
Args:
|
||||
message_id: Message ID
|
||||
|
||||
Returns:
|
||||
Message details
|
||||
"""
|
||||
try:
|
||||
# This would search for the message in the contract
|
||||
# For now, we'll return a mock response
|
||||
return {
|
||||
"success": True,
|
||||
"message": {
|
||||
"message_id": message_id,
|
||||
"topic": "sample_topic_id",
|
||||
"agent_id": "sample_agent_id",
|
||||
"content": "Sample message content",
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting message details: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"error_code": "GET_MESSAGE_FAILED"
|
||||
}
|
||||
|
||||
# Convenience functions for common operations
|
||||
async def create_agent_forum_client(base_url: str, agent_id: str,
|
||||
private_key: str) -> AgentCommunicationClient:
|
||||
"""
|
||||
Create an agent forum client
|
||||
|
||||
Args:
|
||||
base_url: Base URL for the coordinator API
|
||||
agent_id: Agent identifier
|
||||
private_key: Agent's private key
|
||||
|
||||
Returns:
|
||||
Configured communication client
|
||||
"""
|
||||
return AgentCommunicationClient(base_url, agent_id, private_key)
|
||||
|
||||
async def start_forum_discussion(base_url: str, agent_id: str, private_key: str,
|
||||
title: str, description: str, initial_message: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Start a new forum discussion
|
||||
|
||||
Args:
|
||||
base_url: Base URL for the coordinator API
|
||||
agent_id: Agent identifier
|
||||
private_key: Agent's private key
|
||||
title: Discussion title
|
||||
description: Discussion description
|
||||
initial_message: Initial message content
|
||||
|
||||
Returns:
|
||||
Discussion creation result
|
||||
"""
|
||||
client = await create_agent_forum_client(base_url, agent_id, private_key)
|
||||
|
||||
# Create topic
|
||||
topic_result = await client.create_forum_topic(title, description)
|
||||
if not topic_result.get("success"):
|
||||
return topic_result
|
||||
|
||||
# Post initial message
|
||||
message_result = await client.post_message(
|
||||
topic_result["topic_id"],
|
||||
initial_message,
|
||||
"post"
|
||||
)
|
||||
|
||||
return {
|
||||
"success": message_result.get("success", False),
|
||||
"topic_id": topic_result["topic_id"],
|
||||
"message_id": message_result.get("message_id"),
|
||||
"topic": topic_result.get("topic"),
|
||||
"message": message_result.get("message")
|
||||
}
|
||||
Reference in New Issue
Block a user