1176222b7c
Wire an optional `task=<key>` kwarg through the PluginLlm facade so a plugin can route an LLM call through an auxiliary model slot it registered via `ctx.register_auxiliary_task`. Registration already existed; this adds the missing consumption half. Closes #44673. Sub-issue 08/14 of the plugin-interface expansion tracking issue #64182. - New optional `task:` kwarg on complete/acomplete/complete_structured/ acomplete_structured. Unset or "auto" keeps today's main-model path byte-for-byte (task=None reaches call_llm exactly as before), so no prompt-cache or default-behavior change. - A set task resolves provider/model through `auxiliary.<task>` via the existing auxiliary_client path, identical to built-in aux tasks. - Trust gate (per the round-2 design correction): a plugin may only pass a key it registered itself; a built-in key additionally requires `plugins.entries.<id>.llm.allow_task_override: true`. A foreign or unknown key is rejected with a PluginLlmTrustError and a logged warning naming the offending plugin and key -- fail loud, NOT a silent fallback to auto (which would mask misconfiguration and could route to the main model the user steered elsewhere). - The plugin_llm audit dict and audit-log line gain a `task` field. - register_auxiliary_task now stores the plugin's canonical id (`key or name`, the same id ctx.llm is bound to) as the slot owner, so the trust gate matches ownership even when a manifest sets a distinct key. For the common no-key case this equals the name (unchanged). Tests (tests/agent/test_plugin_llm_task_routing.py, 24): _check_task resolution incl. own/foreign/unknown/built-in-gated keys and loud rejection; end-to-end routing sync+async+structured; production-path forwarding into call_llm/async_call_llm (covers the task=None->task line); and ownership resolution against the real plugin registry incl. the name/key reconciliation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013b1XyXitAxV7phGmKWigJX