mirror of
https://github.com/smkrv/ha-text-ai.git
synced 2026-07-22 07:03:58 +08:00
fix: Services survive config entry reload, get_history returns a dict response
- service registration extracted to idempotent _async_register_services,
called from both async_setup and async_setup_entry: unloading the last
entry unregisters services, and a reload (every options change) never
re-ran async_setup, leaving the integration without services
- get_history handler wraps the list in {"history": [...]}: HA rejects
non-dict action responses, so every return_response call failed with a
server error since the service gained SupportsResponse (v2.4.x) - the
path never worked, no consumer could depend on the old shape
- README: get_history example shows response_variable usage and the
actual limit clamp semantics
Both found by live smoke test in HA 2026.7 (Docker), not by static review
This commit is contained in:
@@ -283,12 +283,13 @@ data:
|
|||||||
```yaml
|
```yaml
|
||||||
service: ha_text_ai.get_history
|
service: ha_text_ai.get_history
|
||||||
data:
|
data:
|
||||||
limit: 5 # optional, number of conversations to return (1-100)
|
limit: 5 # optional, number of conversations to return (values above 200 are clamped); omit to get the full stored history
|
||||||
filter_model: "gpt-4o" # optional, filter by specific AI model
|
filter_model: "gpt-4o" # optional, filter by specific AI model
|
||||||
start_date: "2025-02-01" # optional, filter conversations from this date
|
start_date: "2025-02-01" # optional, filter conversations from this date
|
||||||
include_metadata: false # optional, include tokens, response time, etc.
|
include_metadata: false # optional, include tokens, response time, etc.
|
||||||
sort_order: "newest" # optional, sort order: "newest" or "oldest"
|
sort_order: "newest" # optional, sort order: "newest" or "oldest"
|
||||||
instance: sensor.ha_text_ai_gpt
|
instance: sensor.ha_text_ai_gpt
|
||||||
|
response_variable: history_result # entries are in history_result.history
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🚀 Advanced Automation Examples with Response Variables
|
## 🚀 Advanced Automation Examples with Response Variables
|
||||||
|
|||||||
@@ -116,6 +116,19 @@ async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool:
|
|||||||
"""Set up the Home Assistant Text AI component."""
|
"""Set up the Home Assistant Text AI component."""
|
||||||
# Initialize domain data storage
|
# Initialize domain data storage
|
||||||
hass.data.setdefault(DOMAIN, {})
|
hass.data.setdefault(DOMAIN, {})
|
||||||
|
_async_register_services(hass)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _async_register_services(hass: HomeAssistant) -> None:
|
||||||
|
"""Register domain services; safe to call again after unload.
|
||||||
|
|
||||||
|
Unloading the last config entry unregisters the services, and a config
|
||||||
|
entry reload (every options change does one) runs unload + setup_entry
|
||||||
|
without re-running async_setup — so setup_entry must be able to bring
|
||||||
|
the services back.
|
||||||
|
"""
|
||||||
|
if hass.services.has_service(DOMAIN, SERVICE_ASK_QUESTION):
|
||||||
|
return
|
||||||
|
|
||||||
async def async_ask_question(call: ServiceCall) -> dict:
|
async def async_ask_question(call: ServiceCall) -> dict:
|
||||||
"""Handle ask_question service with response data."""
|
"""Handle ask_question service with response data."""
|
||||||
@@ -171,17 +184,21 @@ async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool:
|
|||||||
_LOGGER.error("Error clearing history: %s", str(err))
|
_LOGGER.error("Error clearing history: %s", str(err))
|
||||||
raise HomeAssistantError(f"Failed to clear history: {str(err)}") from err
|
raise HomeAssistantError(f"Failed to clear history: {str(err)}") from err
|
||||||
|
|
||||||
async def async_get_history(call: ServiceCall) -> list:
|
async def async_get_history(call: ServiceCall) -> dict:
|
||||||
"""Handle get_history service."""
|
"""Handle get_history service."""
|
||||||
try:
|
try:
|
||||||
coordinator = get_coordinator_by_instance(hass, call.data["instance"])
|
coordinator = get_coordinator_by_instance(hass, call.data["instance"])
|
||||||
return await coordinator.async_get_history(
|
history = await coordinator.async_get_history(
|
||||||
limit=call.data.get("limit"),
|
limit=call.data.get("limit"),
|
||||||
filter_model=call.data.get("filter_model"),
|
filter_model=call.data.get("filter_model"),
|
||||||
start_date=call.data.get("start_date"),
|
start_date=call.data.get("start_date"),
|
||||||
include_metadata=call.data.get("include_metadata", False),
|
include_metadata=call.data.get("include_metadata", False),
|
||||||
sort_order=call.data.get("sort_order", "newest")
|
sort_order=call.data.get("sort_order", "newest")
|
||||||
)
|
)
|
||||||
|
# HA requires action responses to be dicts. The bare list made
|
||||||
|
# every return_response call fail with a server error, so this
|
||||||
|
# path never worked before and the wrapper breaks no consumer.
|
||||||
|
return {"history": history}
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
_LOGGER.error("Error getting history: %s", str(err))
|
_LOGGER.error("Error getting history: %s", str(err))
|
||||||
raise HomeAssistantError(f"Failed to get history: {str(err)}") from err
|
raise HomeAssistantError(f"Failed to get history: {str(err)}") from err
|
||||||
@@ -226,8 +243,6 @@ async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool:
|
|||||||
schema=SERVICE_SCHEMA_SET_SYSTEM_PROMPT
|
schema=SERVICE_SCHEMA_SET_SYSTEM_PROMPT
|
||||||
)
|
)
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
async def async_check_api(session, endpoint: str, headers: dict, provider: str, api_timeout: int = DEFAULT_API_TIMEOUT) -> bool:
|
async def async_check_api(session, endpoint: str, headers: dict, provider: str, api_timeout: int = DEFAULT_API_TIMEOUT) -> bool:
|
||||||
"""Check API availability using provider registry configuration."""
|
"""Check API availability using provider registry configuration."""
|
||||||
try:
|
try:
|
||||||
@@ -353,6 +368,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
hass.data.setdefault(DOMAIN, {})
|
hass.data.setdefault(DOMAIN, {})
|
||||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
hass.data[DOMAIN][entry.entry_id] = coordinator
|
||||||
|
|
||||||
|
# A reload after the last entry was unloaded needs the services back.
|
||||||
|
_async_register_services(hass)
|
||||||
|
|
||||||
_LOGGER.debug("Stored coordinator in hass.data[%s][%s]", DOMAIN, entry.entry_id)
|
_LOGGER.debug("Stored coordinator in hass.data[%s][%s]", DOMAIN, entry.entry_id)
|
||||||
|
|
||||||
# Set up platforms
|
# Set up platforms
|
||||||
|
|||||||
Reference in New Issue
Block a user