home-assistant, influxdb: full nox modules (ADR 0044/0046)
home-assistant: states/call-service/config tools, emits state.changed bounded to actuator/contact domains (not attribute ticks), overridable via a watch allowlist. influxdb: health/buckets/flux-query tools — tools-only, since a time-series DB has no lifecycle event to emit here. Typecheck; manifests parse.
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
// home-assistant's tools — its own code (novox/hq ADR 0044), importing its own client. They return
|
||||
// structured data; the mesh serves them through the sdk's tool harness.
|
||||
|
||||
import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools";
|
||||
import { HomeAssistantClient, type HAEntityState } from "../client.js";
|
||||
|
||||
/** Trim an entity to the fields worth returning — the full attribute bag is large and mostly noise. */
|
||||
function summarize(s: HAEntityState): { entity_id: string; state: string; name?: string; last_changed?: string } {
|
||||
return {
|
||||
entity_id: s.entity_id,
|
||||
state: s.state,
|
||||
name: typeof s.attributes.friendly_name === "string" ? s.attributes.friendly_name : undefined,
|
||||
last_changed: s.last_changed,
|
||||
};
|
||||
}
|
||||
|
||||
export function getHomeAssistantTools(ha: HomeAssistantClient): ToolDefinition[] {
|
||||
return [
|
||||
{
|
||||
name: "homeassistant_states",
|
||||
description:
|
||||
"Entity states from Home Assistant. With no argument, lists every entity; with `entity` (e.g. light.kitchen), returns just that one with its full attributes.",
|
||||
input: { entity: { type: "string", description: "an entity id to fetch one entity; omitted lists all" } },
|
||||
run: async (args) => {
|
||||
if (args.entity) {
|
||||
const s = await ha.getState(String(args.entity));
|
||||
return { entity_id: s.entity_id, state: s.state, attributes: s.attributes, last_changed: s.last_changed };
|
||||
}
|
||||
const states = await ha.getStates();
|
||||
return { count: states.length, entities: states.map(summarize) };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "homeassistant_call_service",
|
||||
description:
|
||||
"Call a Home Assistant service — turn a switch/light on or off, lock a door, etc. Give the domain (e.g. switch), the service (e.g. turn_on), and optionally a target entity and extra data.",
|
||||
input: {
|
||||
domain: { type: "string", description: "the service domain, e.g. light, switch, lock" },
|
||||
service: { type: "string", description: "the service, e.g. turn_on, turn_off, toggle" },
|
||||
entity: { type: "string", description: "the entity id to target (optional)" },
|
||||
data: { type: "object", description: "extra service data merged into the call (optional)" },
|
||||
},
|
||||
run: async (args) => {
|
||||
const data: Record<string, unknown> = { ...(args.data as Record<string, unknown> | undefined) };
|
||||
if (args.entity) data.entity_id = String(args.entity);
|
||||
const changed = await ha.callService(String(args.domain), String(args.service), data);
|
||||
return { changed: changed.map(summarize) };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "homeassistant_config",
|
||||
description: "Home Assistant instance config: version, location, timezone, loaded components.",
|
||||
input: {},
|
||||
run: async () => {
|
||||
const c = await ha.getConfig();
|
||||
return {
|
||||
location: c.location_name,
|
||||
version: c.version,
|
||||
time_zone: c.time_zone,
|
||||
state: c.state,
|
||||
components: c.components?.length ?? 0,
|
||||
};
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// The tools exist only when a token is configured; without one, home-assistant contributes none
|
||||
// rather than failing the whole runtime.
|
||||
registerModuleTools("home-assistant", (env) => {
|
||||
try {
|
||||
return getHomeAssistantTools(HomeAssistantClient.fromEnv(env));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user