Skip to content

feat(home-assistant): add REST coverage for config CRUD, history, and calendars - #239

Merged
l1shen merged 4 commits into
oomol-lab:mainfrom
reopenpilot:feat-home-assistant-rest-coverage
Jul 31, 2026
Merged

feat(home-assistant): add REST coverage for config CRUD, history, and calendars#239
l1shen merged 4 commits into
oomol-lab:mainfrom
reopenpilot:feat-home-assistant-rest-coverage

Conversation

@reopenpilot

Copy link
Copy Markdown
Contributor

Summary

  • add automation, script, and scene config CRUD, reached over REST at /api/config/<component>/config/<key>
  • add history, logbook, calendar, and error log actions from Home Assistant's published REST API
  • fix buildHomeAssistantUrl dropping the instance base path

Fifteen actions in total, all REST. No WebSocket code is touched.

Problem

Two separate gaps, both in the REST surface.

The editable config store was missing entirely. Home Assistant serves automation, script, and scene configuration over REST, but at /api/config/<component>/config/<key> rather than under the endpoints listed in the published REST API reference. The provider could therefore run an automation but never write one. That endpoint family is absent from the reference page, which is the most likely reason it was never covered — the eight actions the provider started with map exactly onto that page and nothing outside it.

There was no time dimension. /api/states answers "what is the temperature now"; nothing answered "how did it get there" or "what tripped that switch overnight". history, logbook, calendars, and error_log are all on the published reference page and were simply never implemented.

Config CRUD

automation, script, and scene differ only in three respects: the path segment, the input field carrying the key, and whether Home Assistant validates that key as a slug (cv.slug for script, cv.string for automation and scene). One descriptor captures those differences and parameterises shared read/write/delete handlers, rather than nine near-identical copies.

The key travels in the path and Home Assistant injects it into the stored entry itself, so the request body is the bare config object. POST is create-or-update: writing to an unused key creates the entry, which is how the UI creates automations.

Both endpoints require an admin token, and they only see entries Home Assistant manages itself — an automation defined in a separate YAML file or a package is not visible and returns not found. The action descriptions say so, because a non-admin token fails at the provider rather than at input validation.

This pairs with validate_config from #227 to close the loop an agent needs for automation work: discover what a device can do, validate a candidate config, write it, then read the logbook to confirm it fired. followUpActions wires that sequence into the catalog so it is discoverable rather than something the caller has to know.

History and diagnostics

get_history exposes minimalResponse and noAttributes because a full-attribute history across several entities is large enough to matter for an agent's context budget. Against a real instance, one entity over 24 hours returned 7219 points; the compact form drops each row from the full state object to {"state", "last_changed"}.

Home Assistant reads minimal_response, no_attributes, and skip_initial_state by presence rather than by value, so false omits the parameter instead of sending 0. significant_changes_only is read by value and defaults to true, so it is encoded normally.

get_error_log needs one caveat: Home Assistant registers that view only when the instance runs with file logging, so it can report not found on an otherwise healthy instance. Rather than surface a bare "Not Found", the handler maps that case to a message explaining why, and the action description repeats it.

Base path fix

normalizeBaseUrl accepts an instance URL with a path, so a Home Assistant behind a reverse proxy at /ha is a supported configuration. buildHomeAssistantUrl assigned the endpoint path over url.pathname, which dropped that prefix and sent every request to /api/... at the origin instead of /ha/api/....

It now appends to the base path. The WebSocket URL builder added in #227 already did this, so the two transports agree. This is a separate commit; it predates both PRs, and the fifteen new paths here would have multiplied it.

Actions

Action Endpoint
get_automation_config / save_automation_config / delete_automation_config GET/POST/DELETE /api/config/automation/config/{id}
get_script_config / save_script_config / delete_script_config GET/POST/DELETE /api/config/script/config/{key}
get_scene_config / save_scene_config / delete_scene_config GET/POST/DELETE /api/config/scene/config/{id}
check_config POST /api/config/core/check_config
get_history GET /api/history/period/{timestamp}
get_logbook GET /api/logbook/{timestamp}
list_calendars GET /api/calendars
list_calendar_events GET /api/calendars/{entity_id}
get_error_log GET /api/error_log

Reference: https://developers.home-assistant.io/docs/api/rest

Verification

  • npm run fix-check, npm test (584 passing)
  • read paths against a real instance: check_config returned valid; get_history returned 7219 points for one entity over 24 hours with the compact form visibly applied; get_logbook returned 4417 entries; list_calendars returned an empty list on an instance with no calendar integration; get_error_log returned the explanatory 404 described above
  • write paths against the same instance, creating and then removing an oc_smoke_test entry in each domain: create returned ok, the read-back showed the key injected into the stored entry, delete returned ok, and each subsequent read reported not found. A non-slug script key was rejected at the provider before any request. A following scan of 3830 entities found nothing left behind.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 05ea0e02-b1ec-4bac-b85a-626d4a4be60b

📥 Commits

Reviewing files that changed from the base of the PR and between dcb3ffc and 6f94c16.

📒 Files selected for processing (4)
  • src/providers/home_assistant/actions.ts
  • src/providers/home_assistant/executors.ts
  • src/providers/home_assistant/runtime-config.ts
  • src/providers/home_assistant/runtime.ts

Summary by CodeRabbit

  • New Features
    • Added Home Assistant actions to retrieve state history, logbook entries, calendars, calendar events, and session error logs.
    • Added configuration management actions to get, save, delete, and validate automations, scripts, and scenes, with automatic refresh of relevant stored results after changes.
  • Bug Fixes
    • Improved reverse-proxy compatibility for Home Assistant API requests.
    • Standardized request validation and improved handling of missing/disabled error-log access.

Walkthrough

Home Assistant adds REST actions for entity history, logbook entries, calendars, calendar events, and error logs. It also adds automation, script, and scene configuration CRUD actions plus configuration validation. Shared schemas, request typing, key validation, result normalization, follow-up action chains, and reverse-proxy-compatible URL construction support these actions. Configuration handlers are registered alongside existing REST and WebSocket handlers.

Sequence Diagram(s)

sequenceDiagram
  participant ProviderExecutor
  participant HomeAssistantActionHandlers
  participant HomeAssistantConfigActionHandlers
  participant HomeAssistantAPI
  ProviderExecutor->>HomeAssistantActionHandlers: invoke REST action
  HomeAssistantActionHandlers->>HomeAssistantAPI: request history, calendar, logbook, or error log data
  HomeAssistantAPI-->>HomeAssistantActionHandlers: return response
  ProviderExecutor->>HomeAssistantConfigActionHandlers: invoke configuration action
  HomeAssistantConfigActionHandlers->>HomeAssistantAPI: get, save, delete, or check configuration
  HomeAssistantAPI-->>HomeAssistantConfigActionHandlers: return normalized result
Loading

Possibly related PRs

  • oomol-lab/open-connector#227: Related Home Assistant provider changes involving configuration action typing and executor handler registration.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title follows the required type(scope): subject format and clearly summarizes the Home Assistant REST additions.
Description check ✅ Passed The description directly matches the changes, covering config CRUD, history/logbook/calendar actions, and the base-path fix.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/providers/home_assistant/actions.ts`:
- Around line 225-245: The get_history output schema currently requires fields
that Home Assistant omits from minimal intermediate history entries. Update the
history item schema used by the get_history output, near the history output
definition, so entity_id, attributes, and last_updated are optional while
preserving required fields and existing stateSchema usage elsewhere; use a
dedicated loose history-state schema if stateSchema is shared.

In `@src/providers/home_assistant/runtime.ts`:
- Around line 130-137: Prevent empty or whitespace-only entity IDs from
producing an unfiltered history query. Add a nearby readHistoryEntityIds helper
that parses entityIds with requiredStringArray, trims and removes blank values,
throws badHomeAssistantRequest when none remain, and returns the comma-joined
IDs; update the history query’s filter_entity_id in queryParams to use this
helper.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 76e459b0-3b64-4734-83ff-04c13e58d380

📥 Commits

Reviewing files that changed from the base of the PR and between 22b5946 and 15b3fa6.

📒 Files selected for processing (4)
  • src/providers/home_assistant/actions.ts
  • src/providers/home_assistant/executors.ts
  • src/providers/home_assistant/runtime-config.ts
  • src/providers/home_assistant/runtime.ts

Comment thread src/providers/home_assistant/actions.ts
Comment thread src/providers/home_assistant/runtime.ts
…quest URLs

normalizeBaseUrl accepts an instance URL with a path, so a Home Assistant
behind a reverse proxy at /ha is a supported configuration. buildHomeAssistantUrl
assigned the endpoint path over url.pathname, which dropped that prefix and sent
every request to /api/... at the origin instead of /ha/api/....

Append to the base path instead. The WebSocket URL builder added alongside the
registry actions already did this, so the two transports now agree.
…ctions

The provider could read current state but had no time dimension: an agent
could see that a sensor reads 21 degrees, not how it got there or what
triggered a switch overnight. These five endpoints are documented in Home
Assistant's REST API and were simply never covered.

get_history exposes minimalResponse and noAttributes because a full-attribute
history over several entities is large enough to matter for an agent's context
budget. Home Assistant reads minimal_response, no_attributes, and
skip_initial_state by presence rather than by value, so false omits the
parameter instead of sending 0.
Home Assistant serves its editable config store over REST at
/api/config/<component>/config/<key>, not over the WebSocket API. It is absent
from the published REST API reference, which is why the provider covered
"run this automation" but never "write this automation".

The three domains differ only in the path segment, the input field carrying the
key, and whether Home Assistant validates that key as a slug, so one descriptor
parameterises shared read/write/delete handlers rather than nine near-copies.

Together with validate_config this closes the automation loop an agent needs:
discover device capabilities, validate a candidate config, write it, then read
the logbook to confirm it fired. followUpActions wires that chain into the
catalog so the sequence is discoverable.

Both endpoints require an admin token and only see entries Home Assistant
manages itself; the action descriptions say so, since a non-admin token fails
at the provider rather than at input validation.
…tity filter

The get_history output schema reused the /api/states shape, which requires
entity_id and last_updated. Home Assistant does not return those for the rows
between the first and last of a period once minimal_response is set, so the
declared contract rejected real responses. A live 7846-point response validates
against the corrected schema and fails against the previous one. History rows
now use their own schema where only state and last_changed are required, and
/api/states keeps the strict shape.

Also normalize the entity filter before sending it. Home Assistant reads a
missing filter_entity_id as every entity and queryParams drops empty strings, so
a list that normalizes to nothing would silently widen a scoped query into a
whole-instance dump. The schema's minItems already rejects an empty array, but
the request should not depend on that alone, and a whitespace-only id passes the
schema while still being unusable.
@reopenpilot
reopenpilot force-pushed the feat-home-assistant-rest-coverage branch from dcb3ffc to 6f94c16 Compare July 30, 2026 14:09
@l1shen
l1shen merged commit cebbe00 into oomol-lab:main Jul 31, 2026
4 checks passed
@reopenpilot
reopenpilot deleted the feat-home-assistant-rest-coverage branch July 31, 2026 03:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants