Add configurable agent_id support and update integration options

- Introduced configurable OpenClaw `agent_id` for manual setup and service overrides.
- Enhanced API client to support routing messages to specific agents.
- Updated integration options to reload automatically upon saving changes.
- Added service descriptions and translations for the new `agent_id` field.
- Updated version to 0.1.58 in manifest.
This commit is contained in:
techartdev
2026-03-07 14:20:24 +02:00
parent 9bcac79241
commit e0c0c7be8e
9 changed files with 76 additions and 8 deletions
+10
View File
@@ -2,6 +2,16 @@
All notable changes to the OpenClaw Home Assistant Integration will be documented in this file. All notable changes to the OpenClaw Home Assistant Integration will be documented in this file.
## [0.1.58] - 2026-03-07
### Added
- Added configurable OpenClaw `agent_id` support for manual setup, integration options, and per-call `openclaw.send_message` service overrides.
- Added `x-openclaw-agent-id` routing support in the API client so messages can target non-`main` OpenClaw agents.
### Changed
- Saving OpenClaw integration options now reloads the integration automatically, so updated `agent_id` and other runtime options apply immediately.
- Added service descriptions and translations for the new `agent_id` field in the configuration UI.
## [0.1.57] - 2026-02-26 ## [0.1.57] - 2026-02-26
### Changed ### Changed
+12
View File
@@ -30,6 +30,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .api import OpenClawApiClient, OpenClawApiError from .api import OpenClawApiClient, OpenClawApiError
from .const import ( from .const import (
ATTR_AGENT_ID,
ATTR_ATTACHMENTS, ATTR_ATTACHMENTS,
ATTR_MESSAGE, ATTR_MESSAGE,
ATTR_MODEL, ATTR_MODEL,
@@ -47,6 +48,7 @@ from .const import (
ATTR_ACCOUNT_ID, ATTR_ACCOUNT_ID,
ATTR_TIMESTAMP, ATTR_TIMESTAMP,
CONF_ADDON_CONFIG_PATH, CONF_ADDON_CONFIG_PATH,
CONF_AGENT_ID,
CONF_GATEWAY_HOST, CONF_GATEWAY_HOST,
CONF_GATEWAY_PORT, CONF_GATEWAY_PORT,
CONF_GATEWAY_TOKEN, CONF_GATEWAY_TOKEN,
@@ -63,6 +65,7 @@ from .const import (
CONF_VOICE_PROVIDER, CONF_VOICE_PROVIDER,
CONF_THINKING_TIMEOUT, CONF_THINKING_TIMEOUT,
CONTEXT_STRATEGY_TRUNCATE, CONTEXT_STRATEGY_TRUNCATE,
DEFAULT_AGENT_ID,
DEFAULT_CONTEXT_MAX_CHARS, DEFAULT_CONTEXT_MAX_CHARS,
DEFAULT_CONTEXT_STRATEGY, DEFAULT_CONTEXT_STRATEGY,
DEFAULT_ENABLE_TOOL_CALLS, DEFAULT_ENABLE_TOOL_CALLS,
@@ -106,6 +109,7 @@ SEND_MESSAGE_SCHEMA = vol.Schema(
vol.Required(ATTR_MESSAGE): cv.string, vol.Required(ATTR_MESSAGE): cv.string,
vol.Optional(ATTR_SESSION_ID): cv.string, vol.Optional(ATTR_SESSION_ID): cv.string,
vol.Optional(ATTR_ATTACHMENTS): vol.All(cv.ensure_list, [cv.string]), vol.Optional(ATTR_ATTACHMENTS): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(ATTR_AGENT_ID): cv.string,
} }
) )
@@ -136,6 +140,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: OpenClawConfigEntry) ->
use_ssl = entry.data.get(CONF_USE_SSL, False) use_ssl = entry.data.get(CONF_USE_SSL, False)
verify_ssl = entry.data.get(CONF_VERIFY_SSL, True) verify_ssl = entry.data.get(CONF_VERIFY_SSL, True)
session = async_get_clientsession(hass, verify_ssl=verify_ssl) session = async_get_clientsession(hass, verify_ssl=verify_ssl)
agent_id: str = entry.options.get(
CONF_AGENT_ID,
entry.data.get(CONF_AGENT_ID, DEFAULT_AGENT_ID),
)
client = OpenClawApiClient( client = OpenClawApiClient(
host=entry.data[CONF_GATEWAY_HOST], host=entry.data[CONF_GATEWAY_HOST],
@@ -144,6 +152,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: OpenClawConfigEntry) ->
use_ssl=use_ssl, use_ssl=use_ssl,
verify_ssl=verify_ssl, verify_ssl=verify_ssl,
session=session, session=session,
agent_id=agent_id,
) )
coordinator = OpenClawCoordinator(hass, client) coordinator = OpenClawCoordinator(hass, client)
@@ -385,6 +394,7 @@ def _async_register_services(hass: HomeAssistant) -> None:
"""Handle the openclaw.send_message service call.""" """Handle the openclaw.send_message service call."""
message: str = call.data[ATTR_MESSAGE] message: str = call.data[ATTR_MESSAGE]
session_id: str = call.data.get(ATTR_SESSION_ID) or "default" session_id: str = call.data.get(ATTR_SESSION_ID) or "default"
call_agent_id: str | None = call.data.get(ATTR_AGENT_ID)
entry_data = _get_first_entry_data(hass) entry_data = _get_first_entry_data(hass)
if not entry_data: if not entry_data:
@@ -420,6 +430,7 @@ def _async_register_services(hass: HomeAssistant) -> None:
message=message, message=message,
session_id=session_id, session_id=session_id,
system_prompt=system_prompt, system_prompt=system_prompt,
agent_id=call_agent_id,
) )
if options.get(CONF_ENABLE_TOOL_CALLS, DEFAULT_ENABLE_TOOL_CALLS): if options.get(CONF_ENABLE_TOOL_CALLS, DEFAULT_ENABLE_TOOL_CALLS):
@@ -433,6 +444,7 @@ def _async_register_services(hass: HomeAssistant) -> None:
), ),
session_id=session_id, session_id=session_id,
system_prompt=system_prompt, system_prompt=system_prompt,
agent_id=call_agent_id,
) )
assistant_message = _extract_assistant_message(response) assistant_message = _extract_assistant_message(response)
+13 -4
View File
@@ -51,6 +51,7 @@ class OpenClawApiClient:
use_ssl: bool = False, use_ssl: bool = False,
verify_ssl: bool = True, verify_ssl: bool = True,
session: aiohttp.ClientSession | None = None, session: aiohttp.ClientSession | None = None,
agent_id: str = "main",
) -> None: ) -> None:
"""Initialize the API client. """Initialize the API client.
@@ -61,6 +62,7 @@ class OpenClawApiClient:
use_ssl: Use HTTPS instead of HTTP. use_ssl: Use HTTPS instead of HTTP.
verify_ssl: Verify SSL certificates (set False for self-signed certs). verify_ssl: Verify SSL certificates (set False for self-signed certs).
session: Optional aiohttp session (reused from HA). session: Optional aiohttp session (reused from HA).
agent_id: Target OpenClaw agent ID (default: "main").
""" """
self._host = host self._host = host
self._port = port self._port = port
@@ -68,6 +70,7 @@ class OpenClawApiClient:
self._use_ssl = use_ssl self._use_ssl = use_ssl
self._verify_ssl = verify_ssl self._verify_ssl = verify_ssl
self._session = session self._session = session
self._agent_id = agent_id
self._base_url = f"{'https' if use_ssl else 'http'}://{host}:{port}" self._base_url = f"{'https' if use_ssl else 'http'}://{host}:{port}"
# ssl=False disables cert verification for self-signed certs; # ssl=False disables cert verification for self-signed certs;
# ssl=None uses default verification. # ssl=None uses default verification.
@@ -82,11 +85,13 @@ class OpenClawApiClient:
"""Update the authentication token (e.g., after addon restart).""" """Update the authentication token (e.g., after addon restart)."""
self._token = token self._token = token
def _headers(self) -> dict[str, str]: def _headers(self, agent_id: str | None = None) -> dict[str, str]:
"""Build request headers with auth token.""" """Build request headers with auth token and agent ID."""
effective_agent = agent_id or self._agent_id or "main"
return { return {
"Authorization": f"Bearer {self._token}", "Authorization": f"Bearer {self._token}",
"Content-Type": "application/json", "Content-Type": "application/json",
"x-openclaw-agent-id": effective_agent,
} }
async def _get_session(self) -> aiohttp.ClientSession: async def _get_session(self) -> aiohttp.ClientSession:
@@ -182,6 +187,7 @@ class OpenClawApiClient:
model: str | None = None, model: str | None = None,
system_prompt: str | None = None, system_prompt: str | None = None,
stream: bool = False, stream: bool = False,
agent_id: str | None = None,
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Send a chat message (non-streaming). """Send a chat message (non-streaming).
@@ -190,6 +196,7 @@ class OpenClawApiClient:
session_id: Optional session/conversation ID. session_id: Optional session/conversation ID.
model: Optional model override. model: Optional model override.
stream: If True, raises ValueError (use async_stream_message). stream: If True, raises ValueError (use async_stream_message).
agent_id: Optional per-call agent ID override.
Returns: Returns:
Complete chat completion response. Complete chat completion response.
@@ -213,7 +220,7 @@ class OpenClawApiClient:
payload["model"] = model payload["model"] = model
# Pass session_id as a custom header or param if supported by gateway # Pass session_id as a custom header or param if supported by gateway
headers = self._headers() headers = self._headers(agent_id=agent_id)
if session_id: if session_id:
headers["X-Session-Id"] = session_id headers["X-Session-Id"] = session_id
headers["x-openclaw-session-key"] = session_id headers["x-openclaw-session-key"] = session_id
@@ -247,6 +254,7 @@ class OpenClawApiClient:
session_id: str | None = None, session_id: str | None = None,
model: str | None = None, model: str | None = None,
system_prompt: str | None = None, system_prompt: str | None = None,
agent_id: str | None = None,
) -> AsyncIterator[str]: ) -> AsyncIterator[str]:
"""Send a chat message and stream the response via SSE. """Send a chat message and stream the response via SSE.
@@ -256,6 +264,7 @@ class OpenClawApiClient:
message: The user message text. message: The user message text.
session_id: Optional session/conversation ID. session_id: Optional session/conversation ID.
model: Optional model override. model: Optional model override.
agent_id: Optional per-call agent ID override.
Yields: Yields:
Content delta strings from the streaming response. Content delta strings from the streaming response.
@@ -275,7 +284,7 @@ class OpenClawApiClient:
if model: if model:
payload["model"] = model payload["model"] = model
headers = self._headers() headers = self._headers(agent_id=agent_id)
if session_id: if session_id:
headers["X-Session-Id"] = session_id headers["X-Session-Id"] = session_id
headers["x-openclaw-session-key"] = session_id headers["x-openclaw-session-key"] = session_id
+14 -1
View File
@@ -20,11 +20,13 @@ try:
ConfigFlow, ConfigFlow,
ConfigFlowResult, ConfigFlowResult,
OptionsFlow, OptionsFlow,
OptionsFlowWithReload,
) )
except ImportError: except ImportError:
from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow
ConfigFlowResult = dict[str, Any] ConfigFlowResult = dict[str, Any]
OptionsFlowWithReload = OptionsFlow
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
@@ -34,6 +36,7 @@ from .const import (
ADDON_SLUG, ADDON_SLUG,
ADDON_SLUG_FRAGMENTS, ADDON_SLUG_FRAGMENTS,
CONF_ADDON_CONFIG_PATH, CONF_ADDON_CONFIG_PATH,
CONF_AGENT_ID,
CONF_GATEWAY_HOST, CONF_GATEWAY_HOST,
CONF_GATEWAY_PORT, CONF_GATEWAY_PORT,
CONF_GATEWAY_TOKEN, CONF_GATEWAY_TOKEN,
@@ -52,6 +55,7 @@ from .const import (
BROWSER_VOICE_LANGUAGES, BROWSER_VOICE_LANGUAGES,
CONTEXT_STRATEGY_CLEAR, CONTEXT_STRATEGY_CLEAR,
CONTEXT_STRATEGY_TRUNCATE, CONTEXT_STRATEGY_TRUNCATE,
DEFAULT_AGENT_ID,
DEFAULT_GATEWAY_HOST, DEFAULT_GATEWAY_HOST,
DEFAULT_GATEWAY_PORT, DEFAULT_GATEWAY_PORT,
DEFAULT_CONTEXT_MAX_CHARS, DEFAULT_CONTEXT_MAX_CHARS,
@@ -411,6 +415,7 @@ class OpenClawConfigFlow(ConfigFlow, domain=DOMAIN):
CONF_GATEWAY_TOKEN: token, CONF_GATEWAY_TOKEN: token,
CONF_USE_SSL: use_ssl, CONF_USE_SSL: use_ssl,
CONF_VERIFY_SSL: verify_ssl, CONF_VERIFY_SSL: verify_ssl,
CONF_AGENT_ID: user_input.get(CONF_AGENT_ID, DEFAULT_AGENT_ID),
}, },
) )
if "base" not in errors: if "base" not in errors:
@@ -429,13 +434,14 @@ class OpenClawConfigFlow(ConfigFlow, domain=DOMAIN):
vol.Required(CONF_GATEWAY_TOKEN): str, vol.Required(CONF_GATEWAY_TOKEN): str,
vol.Optional(CONF_USE_SSL, default=False): bool, vol.Optional(CONF_USE_SSL, default=False): bool,
vol.Optional(CONF_VERIFY_SSL, default=True): bool, vol.Optional(CONF_VERIFY_SSL, default=True): bool,
vol.Optional(CONF_AGENT_ID, default=DEFAULT_AGENT_ID): str,
} }
), ),
errors=errors, errors=errors,
) )
class OpenClawOptionsFlow(OptionsFlow): class OpenClawOptionsFlow(OptionsFlowWithReload):
"""Handle OpenClaw options.""" """Handle OpenClaw options."""
def __init__(self, config_entry: ConfigEntry) -> None: def __init__(self, config_entry: ConfigEntry) -> None:
@@ -453,6 +459,13 @@ class OpenClawOptionsFlow(OptionsFlow):
selected_provider = options.get(CONF_VOICE_PROVIDER, DEFAULT_VOICE_PROVIDER) selected_provider = options.get(CONF_VOICE_PROVIDER, DEFAULT_VOICE_PROVIDER)
schema: dict[Any, Any] = { schema: dict[Any, Any] = {
vol.Optional(
CONF_AGENT_ID,
default=options.get(
CONF_AGENT_ID,
self._config_entry.data.get(CONF_AGENT_ID, DEFAULT_AGENT_ID),
),
): str,
vol.Optional( vol.Optional(
CONF_INCLUDE_EXPOSED_CONTEXT, CONF_INCLUDE_EXPOSED_CONTEXT,
default=options.get( default=options.get(
+3
View File
@@ -23,6 +23,7 @@ CONF_GATEWAY_TOKEN = "gateway_token"
CONF_USE_SSL = "use_ssl" CONF_USE_SSL = "use_ssl"
CONF_VERIFY_SSL = "verify_ssl" CONF_VERIFY_SSL = "verify_ssl"
CONF_ADDON_CONFIG_PATH = "addon_config_path" CONF_ADDON_CONFIG_PATH = "addon_config_path"
CONF_AGENT_ID = "agent_id"
# Options # Options
CONF_INCLUDE_EXPOSED_CONTEXT = "include_exposed_context" CONF_INCLUDE_EXPOSED_CONTEXT = "include_exposed_context"
@@ -36,6 +37,7 @@ CONF_VOICE_PROVIDER = "voice_provider"
CONF_BROWSER_VOICE_LANGUAGE = "browser_voice_language" CONF_BROWSER_VOICE_LANGUAGE = "browser_voice_language"
CONF_THINKING_TIMEOUT = "thinking_timeout" CONF_THINKING_TIMEOUT = "thinking_timeout"
DEFAULT_AGENT_ID = "main"
DEFAULT_INCLUDE_EXPOSED_CONTEXT = True DEFAULT_INCLUDE_EXPOSED_CONTEXT = True
DEFAULT_CONTEXT_MAX_CHARS = 13000 DEFAULT_CONTEXT_MAX_CHARS = 13000
DEFAULT_CONTEXT_STRATEGY = "truncate" DEFAULT_CONTEXT_STRATEGY = "truncate"
@@ -130,6 +132,7 @@ ATTR_SESSION_KEY = "session_key"
ATTR_DRY_RUN = "dry_run" ATTR_DRY_RUN = "dry_run"
ATTR_MESSAGE_CHANNEL = "message_channel" ATTR_MESSAGE_CHANNEL = "message_channel"
ATTR_ACCOUNT_ID = "account_id" ATTR_ACCOUNT_ID = "account_id"
ATTR_AGENT_ID = "agent_id"
ATTR_OK = "ok" ATTR_OK = "ok"
ATTR_RESULT = "result" ATTR_RESULT = "result"
ATTR_ERROR = "error" ATTR_ERROR = "error"
+1 -1
View File
@@ -8,7 +8,7 @@
"iot_class": "local_polling", "iot_class": "local_polling",
"issue_tracker": "https://github.com/techartdev/OpenClawHomeAssistant/issues", "issue_tracker": "https://github.com/techartdev/OpenClawHomeAssistant/issues",
"requirements": [], "requirements": [],
"version": "0.1.57", "version": "0.1.58",
"dependencies": ["conversation"], "dependencies": ["conversation"],
"after_dependencies": ["hassio", "lovelace"] "after_dependencies": ["hassio", "lovelace"]
} }
+9
View File
@@ -24,6 +24,15 @@ send_message:
selector: selector:
text: text:
multiline: true multiline: true
agent_id:
name: Agent ID
description: >
Optional OpenClaw agent ID to route this message to. Overrides the
agent ID configured in the integration options. Defaults to "main".
required: false
example: "main"
selector:
text:
clear_history: clear_history:
name: Clear History name: Clear History
+7 -1
View File
@@ -18,7 +18,8 @@
"gateway_port": "Gateway Port", "gateway_port": "Gateway Port",
"gateway_token": "Gateway Token", "gateway_token": "Gateway Token",
"use_ssl": "Use SSL (HTTPS)", "use_ssl": "Use SSL (HTTPS)",
"verify_ssl": "Verify SSL certificate" "verify_ssl": "Verify SSL certificate",
"agent_id": "Agent ID"
} }
} }
}, },
@@ -37,6 +38,7 @@
"title": "OpenClaw Options", "title": "OpenClaw Options",
"description": "Configure context and tool-calling behavior.", "description": "Configure context and tool-calling behavior.",
"data": { "data": {
"agent_id": "Agent ID (e.g. main)",
"include_exposed_context": "Include exposed entities context", "include_exposed_context": "Include exposed entities context",
"context_max_chars": "Max context characters", "context_max_chars": "Max context characters",
"context_strategy": "When context exceeds max", "context_strategy": "When context exceeds max",
@@ -124,6 +126,10 @@
"attachments": { "attachments": {
"name": "Attachments", "name": "Attachments",
"description": "Optional file attachments." "description": "Optional file attachments."
},
"agent_id": {
"name": "Agent ID",
"description": "Optional OpenClaw agent ID to route this message to. Overrides the configured default. Defaults to \"main\"."
} }
} }
}, },
@@ -18,7 +18,8 @@
"gateway_port": "Gateway Port", "gateway_port": "Gateway Port",
"gateway_token": "Gateway Token", "gateway_token": "Gateway Token",
"use_ssl": "Use SSL (HTTPS)", "use_ssl": "Use SSL (HTTPS)",
"verify_ssl": "Verify SSL certificate" "verify_ssl": "Verify SSL certificate",
"agent_id": "Agent ID"
} }
} }
}, },
@@ -39,6 +40,7 @@
"title": "OpenClaw Options", "title": "OpenClaw Options",
"description": "Configure context and tool-calling behavior.", "description": "Configure context and tool-calling behavior.",
"data": { "data": {
"agent_id": "Agent ID (e.g. main)",
"include_exposed_context": "Include exposed entities context", "include_exposed_context": "Include exposed entities context",
"context_max_chars": "Max context characters", "context_max_chars": "Max context characters",
"context_strategy": "When context exceeds max", "context_strategy": "When context exceeds max",
@@ -126,6 +128,10 @@
"attachments": { "attachments": {
"name": "Attachments", "name": "Attachments",
"description": "Optional file attachments." "description": "Optional file attachments."
},
"agent_id": {
"name": "Agent ID",
"description": "Optional OpenClaw agent ID to route this message to. Overrides the configured default. Defaults to \"main\"."
} }
} }
}, },