mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-30 09:42:21 +01:00
- Enforce vision probe SSRF safe local host validation, SafeProbeBackend TOCTOU elimination, and session owner authorization - Authorize session titling endpoints against registered owner configuration - Sanitize sensitive metadata logging from agent loop debug logs - Add async non-blocking execution for spec router tools and thinking decisions - Harden email cache invalidation fencing and thread-safe mutation - Add regression test suites for legacy browser authority, spec contracts, spec router request scoping, and task MCP delivery
115 lines
4.0 KiB
Python
115 lines
4.0 KiB
Python
"""Shared resolver for background-task AI endpoints."""
|
|
|
|
from src.endpoint_resolver import (
|
|
resolve_endpoint,
|
|
resolve_utility_fallback_candidates,
|
|
same_endpoint_base as _same_endpoint_base,
|
|
)
|
|
from src.llm_core import llm_call_async_with_fallback
|
|
from src.interactive_gate import wait_for_interactive_quiet
|
|
|
|
|
|
def resolve_task_endpoint(fallback_url=None, fallback_model=None, fallback_headers=None, owner=None):
|
|
"""Return (endpoint_url, model, headers) for background tasks.
|
|
|
|
Reads task_endpoint_id / task_model from admin settings.
|
|
Falls back to the provided values when the setting is empty or the
|
|
endpoint cannot be resolved.
|
|
"""
|
|
return resolve_endpoint("task", fallback_url, fallback_model, fallback_headers, owner=owner)
|
|
|
|
|
|
def resolve_task_candidates(
|
|
fallback_url=None,
|
|
fallback_model=None,
|
|
fallback_headers=None,
|
|
override_url=None,
|
|
override_model=None,
|
|
override_headers=None,
|
|
owner=None,
|
|
):
|
|
"""Return ordered background-task LLM candidates.
|
|
|
|
Order:
|
|
1. configured Background Tasks endpoint/model, or caller fallback
|
|
2. Utility endpoint/model
|
|
3. Default endpoint/model
|
|
4. Utility fallback chain
|
|
"""
|
|
candidates = []
|
|
|
|
def _append(url, model, headers):
|
|
if not url or not model:
|
|
return
|
|
key = (url, model)
|
|
if any((u, m) == key for u, m, _ in candidates):
|
|
return
|
|
candidates.append((url, model, headers or {}))
|
|
|
|
if override_url and override_model:
|
|
try:
|
|
from src.database import SessionLocal
|
|
from src.endpoint_resolver import (
|
|
build_chat_url, build_headers, resolve_endpoint_runtime,
|
|
resolve_owner_registered_endpoint,
|
|
)
|
|
db = SessionLocal()
|
|
try:
|
|
ep = resolve_owner_registered_endpoint(db, override_url, owner)
|
|
runtime_base, api_key = resolve_endpoint_runtime(ep, owner=owner)
|
|
_append(build_chat_url(runtime_base), override_model, build_headers(api_key, runtime_base))
|
|
finally:
|
|
db.close()
|
|
except Exception:
|
|
# A missing, foreign, disabled or unreadable registration is not
|
|
# authority to contact a caller-provided endpoint or use its key.
|
|
pass
|
|
_append(*resolve_task_endpoint(fallback_url, fallback_model, fallback_headers, owner=owner))
|
|
_append(*resolve_endpoint("utility", owner=owner))
|
|
_append(*resolve_endpoint("default", owner=owner))
|
|
for url, model, headers in resolve_utility_fallback_candidates(owner=owner):
|
|
_append(url, model, headers)
|
|
return candidates
|
|
|
|
|
|
async def task_llm_call_async(
|
|
messages,
|
|
*,
|
|
fallback_url=None,
|
|
fallback_model=None,
|
|
fallback_headers=None,
|
|
override_url=None,
|
|
override_model=None,
|
|
override_headers=None,
|
|
owner=None,
|
|
**kwargs,
|
|
):
|
|
"""Call the shared background-task LLM candidate chain."""
|
|
resolver_kwargs = {
|
|
"fallback_url": fallback_url,
|
|
"fallback_model": fallback_model,
|
|
"fallback_headers": fallback_headers,
|
|
"owner": owner,
|
|
}
|
|
if override_url is not None:
|
|
resolver_kwargs["override_url"] = override_url
|
|
if override_model is not None:
|
|
resolver_kwargs["override_model"] = override_model
|
|
if override_headers is not None:
|
|
resolver_kwargs["override_headers"] = override_headers
|
|
candidates = resolve_task_candidates(
|
|
**resolver_kwargs,
|
|
)
|
|
if not candidates:
|
|
raise RuntimeError("No LLM endpoint available for background task")
|
|
await wait_for_interactive_quiet("background task LLM")
|
|
kwargs.setdefault("workload", "background")
|
|
if kwargs.get("reasoning_effort") is None:
|
|
kwargs.pop("reasoning_effort", None)
|
|
from src.utility_effort import candidate_effort_factory
|
|
|
|
factory = candidate_effort_factory(owner)
|
|
if factory is not None:
|
|
kwargs.setdefault("candidate_request_factory", factory)
|
|
return await llm_call_async_with_fallback(candidates, messages=messages, **kwargs)
|