The Assist API's built-in tools (date/time, calendar, todo, script, live
context, intents) now come from the llm integration's tool platform, so the
llm integration must be loaded for an Assist LLM API to expose them. Add llm
to conversation's dependencies so it loads wherever the conversation stack
(and thus any LLM agent) is active.
This makes the registry tools reach Assist consumers, which surfaces in
anthropic's test_extended_thinking_tool_call: it patched
AssistAPI._async_get_tools to control the tool set, but tools now also come
from the registry. Empty the registry in that test so its tool set stays the
single mock tool.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Relocate the intent-tool wrapping (and its two prompt fragments,
DEVICE_CONTROL_TOOL_USAGE_PROMPT and the no-timer note) out of AssistAPI
into a core provider registered by the llm integration. This is a
behavior-preserving relocation: every intent that was wrapped before is
still wrapped, with byte-identical tool names and prompt lines.
The new homeassistant/components/llm/intents.py owns IGNORE_INTENTS, the
timer-intent set, the slugify cache and the device-control prompt, and
registers an intent_tools provider in the llm integration's async_setup
(before GetDateTime/live-context so the tool order is unchanged). The
provider reproduces AssistAPI's exposure/timer filtering exactly and only
emits its prompt when entities are actually exposed.
AssistAPI._async_get_tools now returns [] and the api_prompt no longer
hardcodes the device-control or no-timer fragments; the now-unused imports
and helpers are removed. The order-independent parity snapshot
(test_assist_api_snapshot) stays green without regeneration.
llm gains an after_dependencies on intent (its provider uses intent
component helpers, which are safe without intent being set up). mcp_server,
which consumes the assist API directly, now depends on llm so its
registered tools are available.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
test_dynamic_time_injection case 3 relies on the Assist API providing the
GetDateTime tool (which suppresses the dynamic time prompt). Since that tool
now comes from the llm integration's registry rather than being hardcoded in
AssistAPI, the test must set up the llm integration; otherwise GetDateTime is
absent and the time prompt is injected, failing the assertion.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move GetLiveContextTool and its DYNAMIC_CONTEXT_PROMPT prompt fragment out of
AssistAPI into the llm integration. The tool is now contributed by a registered
provider (with its prompt via LLMTools(prompt=...)) that returns it only when
entities are exposed, reproducing the previous exposure-dependent behaviour.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
test_calendar_get_events_tool and test_todo_get_items_tool set up their
integration last and then called async_get_api before the llm integration's
EVENT_COMPONENT_LOADED-driven platform discovery had registered the provider,
so they passed in the full-file run (later setups cycled the loop) but failed
in isolation / under randomized ordering. Add async_block_till_done() before
async_get_api so discovery completes deterministically.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move ScriptTool (and its ActionTool base, the _get_cached_action_parameters
helper and the action-parameters cache it owns) out of AssistAPI into a
script/llm.py tool platform discovered by the llm integration, mirroring
calendar/llm.py and todo/llm.py. ActionTool was used exclusively by
ScriptTool, so the whole unit moves together; helpers/llm.py keeps only the
SCRIPT_DOMAIN import it still needs for _get_exposed_entities.
The provider reuses llm.async_get_exposed_entities to reproduce the exact
exposed-script entities AssistAPI fed the tool, keeping the emitted
ScriptTools byte-identical. The parity snapshot stays unchanged: script was
already set up in test_assist_api_snapshot, so the platform is discovered
without surfacing any new tools.
The ScriptTool tests in test_llm.py now set up the llm component (so the
platform is discovered) and reference the tool and cache via the script
component root (script.llm) to satisfy the component-root import rule.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move TodoGetItemsTool out of AssistAPI into a todo/llm.py tool platform
discovered by the llm integration, mirroring calendar/llm.py. The provider
reuses llm.async_get_exposed_entities to reproduce the exact exposed
to-do list names AssistAPI fed the tool, keeping todo_get_items
byte-identical.
The parity snapshot now sets up the todo component (required to load the
platform), which also registers todo's intent handlers. This additively
surfaces the HassListAddItem/HassListCompleteItem/HassListRemoveItem
intent tools in the snapshot; the moved todo_get_items tool and the
prompt are unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move CalendarGetEventsTool out of AssistAPI into a calendar/llm.py tool
platform discovered by the llm integration. Add a public
async_get_exposed_entities helper so the provider reproduces the exact
exposed-calendar names AssistAPI fed the tool, keeping the parity snapshot
byte-identical.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the GetDateTimeTool class out of AssistAPI's hardcoded built-in list
and register it from the llm integration's async_setup via the tool
registry. Net behavior is unchanged: GetDateTime is still exposed by the
Assist API, it just comes from the registry now.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The v1 tool-platform refactor moves built-in tools and prompt fragments out
of AssistAPI into per-integration registrations, which changes their order
but not their content. Compare the tool set as a name-keyed mapping and the
prompt as a set of lines, so each migration step verifies same-tools /
same-content / same-prompt regardless of ordering. Final ordering parity with
dev is checked separately at the end of the refactor.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AssistAPI.async_get_api_instance now also pulls registered tool
providers for the assist API, appending their tools and prompt
fragments to the hardcoded built-ins. Purely additive: with no
registrations, behaviour is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New system integration that owns the LLM tools platform: its async_setup
drives async_process_integration_platforms(hass, "llm", ...) so integrations
can ship an <integration>/llm.py with an async_setup_tools hook to register
tools, mirroring the intent helper/integration split. The framework (registry,
Tool, APIs) stays in homeassistant.helpers.llm. No tools are registered yet,
so behavior is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduce a per-tool registration layer in helpers/llm.py: an LLMTools
result (tools + optional prompt fragment), a provider callback type, and
async_register_tool_provider / async_register_tool registering into one or
more API ids. Providers are stored in a registry and merged by
_async_get_registered_tools. Nothing consumes the registry yet, so there is
no behavior change (the Assist parity snapshot is unchanged); AssistAPI is
wired to it in a later step.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Capture the assembled Assist prompt and the full serialized tool set
(name, description, parameters) for a scenario exercising every built-in
tool type — intents, timer tools, calendar/todo/script tools, GetDateTime
and GetLiveContext. This is the parity net for the upcoming LLM tool
platform refactor: the snapshot must stay identical as tools and intents
move out of AssistAPI into per-integration platforms.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>