mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
dont make it always get the actual thingy perfect
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
function getConfiguredEntities(homeAssistantService) {
|
||||
const entities = homeAssistantService.getState()?.entities;
|
||||
return Array.isArray(entities) ? entities : [];
|
||||
}
|
||||
|
||||
function resolveConfiguredEntity(homeAssistantService, rawEntityId, toolId) {
|
||||
const entityId = String(rawEntityId || '').trim();
|
||||
if (!entityId) throw new Error(`${toolId} requires args.entity_id`);
|
||||
|
||||
const entities = getConfiguredEntities(homeAssistantService);
|
||||
const exact = entities.find((entry) => String(entry?.id || '') === entityId);
|
||||
if (exact) return { entity: exact, entityId: String(exact.id) };
|
||||
|
||||
// LLM tool calls sometimes drop the Home Assistant domain prefix and send
|
||||
// shelf_rgb_bulb instead of light.shelf_rgb_bulb. Suffix matching keeps that
|
||||
// forgiving behavior inside the configured-entity allowlist, so the model can
|
||||
// recover from a naming slip without gaining access to arbitrary HA entities.
|
||||
const suffixMatches = entities.filter((entry) => {
|
||||
const configuredId = String(entry?.id || '');
|
||||
return configuredId.endsWith(`.${entityId}`);
|
||||
});
|
||||
|
||||
if (suffixMatches.length === 1) {
|
||||
return { entity: suffixMatches[0], entityId: String(suffixMatches[0].id) };
|
||||
}
|
||||
|
||||
// Ambiguous suffixes are rejected because picking one would be riskier than
|
||||
// asking the model/user to use the full entity id. The error names the suffix
|
||||
// issue directly so future logs point at the real failure mode.
|
||||
if (suffixMatches.length > 1) {
|
||||
throw new Error(`${toolId} entity_id ambiguous; use the full Home Assistant entity id`);
|
||||
}
|
||||
|
||||
throw new Error(`${toolId} entity_id not configured`);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
resolveConfiguredEntity,
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
const { resolveConfiguredEntity } = require('./haEntityLookup');
|
||||
|
||||
module.exports = {
|
||||
id: 'ha_set_entity',
|
||||
signature: 'ha_set_entity(entity_id, state)',
|
||||
@@ -23,18 +25,17 @@ module.exports = {
|
||||
return { available: true, reason: null };
|
||||
},
|
||||
async execute({ args = {}, homeAssistantService }) {
|
||||
const entityId = String(args?.entity_id || args?.entityId || '').trim();
|
||||
if (!entityId) throw new Error('ha_set_entity requires args.entity_id');
|
||||
|
||||
// Configured Home Assistant entities are the room-control surface the
|
||||
// overseer is allowed to use. Some physical room lights are exposed by Home
|
||||
// Assistant as switches because they are outlet-backed lamps, so this tool
|
||||
// intentionally permits every configured entity for on/off control instead
|
||||
// of limiting itself to HA's light domain.
|
||||
const allowed = new Set(
|
||||
(homeAssistantService.getState()?.entities || []).map((entry) => String(entry?.id || '')).filter(Boolean),
|
||||
const { entityId } = resolveConfiguredEntity(
|
||||
homeAssistantService,
|
||||
args?.entity_id || args?.entityId,
|
||||
'ha_set_entity',
|
||||
);
|
||||
if (!allowed.has(entityId)) throw new Error('ha_set_entity entity_id not configured');
|
||||
|
||||
const state = String(args?.state || '').toLowerCase();
|
||||
if (state !== 'on' && state !== 'off') throw new Error('ha_set_entity requires args.state of on/off');
|
||||
await homeAssistantService.setEntityState(entityId, state, { source: 'overseerControl' });
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
const { resolveConfiguredEntity } = require('./haEntityLookup');
|
||||
|
||||
function normalizeColorHex(value) {
|
||||
const raw = String(value || '').trim();
|
||||
const withoutHash = raw.startsWith('#') ? raw.slice(1) : raw;
|
||||
@@ -24,15 +26,6 @@ function normalizeColorHex(value) {
|
||||
return `#${expanded.toLowerCase()}`;
|
||||
}
|
||||
|
||||
function findConfiguredEntity(homeAssistantService, entityId) {
|
||||
const entities = homeAssistantService.getState()?.entities || [];
|
||||
|
||||
// The overseer only gets to act on entities that the local config already
|
||||
// exposes. This mirrors ha_set_entity and prevents a model-generated entity id
|
||||
// from becoming an arbitrary Home Assistant service call.
|
||||
return entities.find((entry) => String(entry?.id || '') === entityId) || null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
id: 'ha_set_light_color',
|
||||
signature: 'ha_set_light_color(entity_id, color_hex)',
|
||||
@@ -62,15 +55,17 @@ module.exports = {
|
||||
return { available: true, reason: null };
|
||||
},
|
||||
async execute({ args = {}, homeAssistantService }) {
|
||||
const entityId = String(args?.entity_id || args?.entityId || '').trim();
|
||||
if (!entityId) throw new Error('ha_set_light_color requires args.entity_id');
|
||||
const resolved = resolveConfiguredEntity(
|
||||
homeAssistantService,
|
||||
args?.entity_id || args?.entityId,
|
||||
'ha_set_light_color',
|
||||
);
|
||||
const { entity, entityId } = resolved;
|
||||
|
||||
// Accepting args.color as a compatibility alias keeps manual/internal calls
|
||||
// forgiving, while the public tool schema still teaches the model to send
|
||||
// the clearer color_hex argument.
|
||||
const colorHex = normalizeColorHex(args?.color_hex ?? args?.colorHex ?? args?.color);
|
||||
const entity = findConfiguredEntity(homeAssistantService, entityId);
|
||||
if (!entity) throw new Error('ha_set_light_color entity_id not configured');
|
||||
|
||||
// setLightColor already requires a HA light, but checking the normalized
|
||||
// entity state here gives the overseer a more specific error and blocks
|
||||
|
||||
Reference in New Issue
Block a user