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
21 changes: 20 additions & 1 deletion dapr_agents/agents/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
from datetime import datetime, timezone
from typing import Any, Dict, Iterable, List, Optional, Sequence, Union, Coroutine

from dapr.clients import DaprClient
from dapr.clients.grpc._response import GetMetadataResponse, RegisteredComponents

from dapr_agents.agents.components import AgentComponents
from dapr_agents.agents.configs import (
AgentMemoryConfig,
Expand Down Expand Up @@ -124,11 +127,27 @@ def __init__(
workflow_grpc_options=workflow_grpc,
)

if memory is None:
with DaprClient() as _client:
resp: GetMetadataResponse = _client.get_metadata()
components: Sequence[RegisteredComponents] = resp.registered_components
for component in components:
if (
"state" in component.type
and component.name == "agent-statestore"
):
memory = AgentMemoryConfig(
store=ConversationDaprStateMemory(
store_name="agent-statestore",
session_id=f"{name.replace(' ', '-').lower() if name else 'default'}-session",
)
)

# -----------------------------
# Memory wiring
# -----------------------------
self._memory = memory or AgentMemoryConfig()
if self._memory.store is None and state is not None:
if self._memory.store and state is not None:
# Auto-provision a Dapr-backed memory if we have a state store.
self._memory.store = ConversationDaprStateMemory( # type: ignore[union-attr]
store_name=state.store.store_name,
Expand Down
38 changes: 37 additions & 1 deletion dapr_agents/agents/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
from datetime import datetime, timezone
from typing import Any, Callable, Dict, Optional, Sequence

from dapr.clients import DaprClient
from dapr.clients.grpc._state import Concurrency, Consistency, StateOptions
from dapr.clients.grpc._response import GetMetadataResponse, RegisteredComponents
from pydantic import BaseModel, ValidationError

from dapr_agents.agents.configs import (
Expand All @@ -18,7 +20,10 @@
StateModelBundle,
)
from dapr_agents.agents.schemas import AgentWorkflowEntry
from dapr_agents.storage.daprstores.stateservice import StateStoreError
from dapr_agents.storage.daprstores.stateservice import (
StateStoreError,
StateStoreService,
)
from dapr_agents.types.workflow import DaprWorkflowStatus

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -63,6 +68,37 @@ def __init__(
self.name = name
self._workflow_grpc_options = workflow_grpc_options

if pubsub is None or state is None or registry is None:
with DaprClient() as _client:
resp: GetMetadataResponse = _client.get_metadata()
components: Sequence[RegisteredComponents] = resp.registered_components
for component in components:
if (
"state" in component.type
and component.name == "agent-wfstatestore"
and state is None
):
state = AgentStateConfig(
store=StateStoreService(store_name=component.name),
state_key=f"{name.replace(' ', '-').lower() if name else 'default'}:workflow_state",
)
if component.name == "agent-registry" and registry is None:
registry = AgentRegistryConfig(
store=StateStoreService(store_name="agent-registry"),
team_name="default",
)
if (
"pubsub" in component.type
and component.name == "agent-pubsub"
and pubsub is None
):
logger.info(f"topic: {name}.topic")
pubsub = AgentPubSubConfig(
pubsub_name="agent-pubsub",
agent_topic=f"{name.replace(' ', '-').lower()}.topic",
broadcast_topic="agents.broadcast",
)

# -----------------------------
# Pub/Sub configuration (copy)
# -----------------------------
Expand Down
8 changes: 8 additions & 0 deletions tests/agents/durableagent/test_durable_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ def __exit__(self, exc_type, exc_val, exc_tb):
def __call__(self, *args, **kwargs):
return self

def get_metadata(self):
"""Mock get_metadata that returns empty metadata."""
from unittest.mock import MagicMock

response = MagicMock()
response.registered_components = []
return response


class TestDurableAgent:
"""Test cases for the DurableAgent class."""
Expand Down
8 changes: 8 additions & 0 deletions tests/agents/durableagent/test_mcp_streamable_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ class R:
def execute_state_transaction(self, *args, **kwargs):
pass

def get_metadata(self):
"""Mock get_metadata that returns empty metadata."""
from unittest.mock import MagicMock

response = MagicMock()
response.registered_components = []
return response

statestore.DaprClient = MockDaprClient

# Patch out agent registration logic (skip state store entirely)
Expand Down
6 changes: 6 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ def delete_state(self, store_name, key, **kwargs):
"""Mock delete_state that does nothing."""
pass

def get_metadata(self):
"""Mock get_metadata that returns empty metadata."""
response = MagicMock()
response.registered_components = []
return response


class MockConversationInput:
pass
Expand Down