Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 9 additions & 17 deletions python/valuecell/core/__init__.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,31 @@
# Session management
from .agent.decorator import create_wrapped_agent
from .agent.responses import notification, streaming
from .session import (
InMemoryMessageStore,
InMemorySessionStore,
Message,
MessageStore,
Role,
Session,
SessionStatus,
SessionManager,
SessionStatus,
SessionStore,
MessageStore,
InMemoryMessageStore,
SQLiteMessageStore,
)

# Task management
from .task import (
InMemoryTaskStore,
Task,
TaskManager,
TaskStatus,
TaskStore,
)
from .task import InMemoryTaskStore, Task, TaskManager, TaskStatus, TaskStore

# Type system
from .types import (
UserInput,
UserInputMetadata,
BaseAgent,
StreamResponse,
RemoteAgentResponse,
StreamResponse,
UserInput,
UserInputMetadata,
)

from .agent.decorator import serve, create_wrapped_agent
from .agent.responses import streaming, notification

__all__ = [
# Session exports
"Message",
Expand All @@ -58,7 +51,6 @@
"StreamResponse",
"RemoteAgentResponse",
# Agent utilities
"serve",
"create_wrapped_agent",
# Response utilities
"streaming",
Expand Down
26 changes: 11 additions & 15 deletions python/valuecell/core/agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,17 +122,17 @@ await connections.stop_agent("AgentName")
await connections.stop_all()
```

#### Remote Agent Support
#### Remote and Local Are Unified

```python
# List remote Agents
remote_agents = connections.list_remote_agents()

# Get remote Agent configuration
card_data = connections.get_remote_agent_card("RemoteAgentName")
# All available agents (local + configured URL-only)
available_agents = connections.list_available_agents()

# Get Agent information
# Get Agent information (if implemented)
agent_info = connections.get_agent_info("AgentName")

# Get Agent card (returns None if unavailable; set fetch_if_missing=True to fetch remotely)
card = connections.get_agent_card("AgentName", fetch_if_missing=False)
```

### 3. AgentClient Class
Expand Down Expand Up @@ -250,13 +250,9 @@ async def remote_demo():
available_agents = connections.list_available_agents()
logger.info(f"Available Agents: {available_agents}")

# List remote Agents
remote_agents = connections.list_remote_agents()
logger.info(f"Remote Agents: {remote_agents}")

# Connect to remote Agent (if any)
if remote_agents:
agent_name = remote_agents[0]
# Connect to any available Agent (including URL-only)
if available_agents:
agent_name = available_agents[0]
try:
agent_url = await connections.start_agent(agent_name)
logger.info(f"Successfully connected to remote Agent {agent_name}: {agent_url}")
Expand All @@ -272,7 +268,7 @@ async def remote_demo():
logger.info(f"Remote response: {response}")

except Exception as e:
logger.error(f"Failed to connect to remote Agent {agent_name}: {e}")
logger.error(f"Failed to connect to Agent {agent_name}: {e}")

if __name__ == "__main__":
asyncio.run(remote_demo())
Expand Down
2 changes: 0 additions & 2 deletions python/valuecell/core/agent/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@
# Core agent functionality
from .client import AgentClient
from .connect import RemoteConnections
from .decorator import serve
from .registry import AgentRegistry

__all__ = [
# Core agent exports
"AgentClient",
"RemoteConnections",
"serve",
"AgentRegistry",
]
65 changes: 65 additions & 0 deletions python/valuecell/core/agent/card.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import json
from pathlib import Path
from typing import Optional

from a2a.types import AgentCapabilities, AgentCard
from valuecell.utils import get_agent_card_path


def parse_local_agent_card_dict(agent_card_dict: dict) -> Optional[AgentCard]:
if not isinstance(agent_card_dict, dict):
return None
if "enabled" in agent_card_dict:
del agent_card_dict["enabled"]
if "description" not in agent_card_dict:
agent_card_dict["description"] = (
f"No description available for {agent_card_dict.get('name', 'unknown')} agent."
)
if "capabilities" not in agent_card_dict:
agent_card_dict["capabilities"] = AgentCapabilities(
streaming=True, push_notifications=False
).model_dump()

agent_card = AgentCard.model_validate(agent_card_dict)
return agent_card


def find_local_agent_card_by_agent_name(
agent_name: str, ignore_disabled: bool = True, base_dir: Optional[str | Path] = None
) -> Optional[AgentCard]:
"""
Reads JSON files from agent_cards directory and returns the first one where name matches.

Args:
name: The agent name to search for

Returns:
Dict: The agent configuration dictionary if found, None otherwise
"""
agent_cards_path = Path(base_dir) if base_dir else Path(get_agent_card_path())

# Check if the agent_cards directory exists
if not agent_cards_path.exists():
return None

# Iterate through all JSON files in the agent_cards directory
for json_file in agent_cards_path.glob("*.json"):
try:
with open(json_file, "r", encoding="utf-8") as f:
agent_card_dict = json.load(f)

# Check if this agent config has the matching name
if not isinstance(agent_card_dict, dict):
continue
if agent_card_dict.get("name") != agent_name:
continue
if agent_card_dict.get("enabled", True) is False and ignore_disabled:
continue
return parse_local_agent_card_dict(agent_card_dict)

except (json.JSONDecodeError, IOError):
# Skip files that can't be read or parsed
continue

# Return None if no matching agent is found
return None
11 changes: 6 additions & 5 deletions python/valuecell/core/agent/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ class AgentClient:
def __init__(self, agent_url: str, push_notification_url: str = None):
self.agent_url = agent_url
self.push_notification_url = push_notification_url
self.agent_card = None
self._client = None
self._httpx_client = None
self._initialized = False

async def _ensure_initialized(self):
async def ensure_initialized(self):
if not self._initialized:
await self._setup_client()
self._initialized = True
Expand Down Expand Up @@ -44,8 +45,8 @@ async def _setup_client(self):

client_factory = ClientFactory(config)
card_resolver = A2ACardResolver(self._httpx_client, self.agent_url)
card = await card_resolver.get_agent_card()
self._client = client_factory.create(card)
self.agent_card = await card_resolver.get_agent_card()
self._client = client_factory.create(self.agent_card)

async def send_message(
self,
Expand All @@ -59,7 +60,7 @@ async def send_message(
If `streaming` is True, return an async iterator producing (task, event) pairs.
If `streaming` is False, return the first (task, event) pair (and close the generator).
"""
await self._ensure_initialized()
await self.ensure_initialized()

message = Message(
role=Role.user,
Expand Down Expand Up @@ -87,7 +88,7 @@ async def wrapper() -> AsyncIterator[RemoteAgentResponse]:
return wrapper()

async def get_agent_card(self):
await self._ensure_initialized()
await self.ensure_initialized()
card_resolver = A2ACardResolver(self._httpx_client, self.agent_url)
return await card_resolver.get_agent_card()

Expand Down
Loading