Scope auxiliary LLM endpoints by owner (#2996)

* fix(auth): scope auxiliary llm endpoints by owner

* fix(auth): scope auxiliary llm fallbacks by owner
This commit is contained in:
Vykos
2026-06-07 14:47:44 +02:00
committed by GitHub
parent 12a7e741d0
commit 83b0ab7cd3
11 changed files with 146 additions and 32 deletions
+67
View File
@@ -0,0 +1,67 @@
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def _src(path: str) -> str:
return (ROOT / path).read_text(encoding="utf-8")
def test_registered_manual_compaction_uses_session_owner_for_utility_endpoint():
session_src = _src("routes/session_routes.py")
assert 'owner = getattr(session, "owner", None) or effective_user(request)' in session_src
assert 'resolve_endpoint("utility", owner=owner)' in session_src
def test_task_name_generation_uses_owner_scoped_session_endpoint():
src = _src("routes/task_routes.py")
assert "async def _generate_task_name(prompt: str, owner: Optional[str] = None)" in src
assert "q = q.filter(DbSession.owner == owner)" in src
assert "headers = recent.headers or {}" in src
assert "headers=headers" in src
assert "await _generate_task_name(req.prompt, owner=user)" in src
def test_auto_compaction_utility_endpoint_keeps_chat_owner():
helper_src = _src("routes/chat_helpers.py")
compact_src = _src("src/context_compactor.py")
assert "owner=user" in helper_src
assert "owner: Optional[str] = None" in compact_src
assert 'resolve_endpoint("utility", owner=owner)' in compact_src
def test_background_session_sort_uses_owner_task_endpoint():
src = _src("src/session_actions.py")
assert "resolve_task_endpoint(owner=owner or None)" in src
def test_scheduler_fallbacks_and_research_headers_are_owner_scoped():
src = _src("src/task_scheduler.py")
assert "resolve_utility_fallback_candidates(owner=task.owner or None)" in src
assert 'resolve_endpoint(\n "research",' in src
assert "owner=task.owner or None" in src
assert "headers_from_resolver = False" in src
assert "headers_from_resolver = True" in src
assert "from src.auth_helpers import owner_filter" in src
assert "owner_filter(ep_q, ModelEndpoint, task.owner or None)" in src
def test_research_routes_fallbacks_are_owner_scoped():
src = _src("routes/research_routes.py")
assert 'resolve_endpoint("research", owner=user)' in src
assert 'resolve_endpoint("utility", owner=user)' in src
assert 'resolve_endpoint("default", owner=user)' in src
assert 'resolve_endpoint("chat", owner=user)' in src
assert '_merge(*resolve_endpoint("chat", owner=user))' in src
assert '_merge(*resolve_endpoint("research", owner=user))' in src
assert '_merge(*resolve_endpoint("utility", owner=user))' in src
assert "ep = _owned_enabled_endpoint(db, user)" in src
assert "db.query(ModelEndpoint).filter(ModelEndpoint.is_enabled == True).first()" not in src
assert "owner = getattr(sess, \"owner\", None) or None" in src
+1 -1
View File
@@ -133,7 +133,7 @@ class TestMaybeCompactFourthMessage:
cc.get_context_length = lambda url, model: context_length
cc.llm_call_async = _fake_summary
cc.resolve_endpoint = lambda which: (None, None, None)
cc.resolve_endpoint = lambda which, owner=None: (None, None, None)
cc._update_session_history = lambda *a, **k: None
try:
return asyncio.run(
+29 -2
View File
@@ -79,6 +79,7 @@ class _FakeSession:
endpoint_url = "http://example.test/v1"
model = "test-model"
headers = {}
owner = "session-owner"
def __init__(self, history):
self.history = history
@@ -107,7 +108,11 @@ def _compact_prompt_for(monkeypatch, history):
import src.model_context as model_context
monkeypatch.setattr(agent_runs, "is_active", lambda session_id: False)
monkeypatch.setattr(endpoint_resolver, "resolve_endpoint", lambda kind, owner=None: (None, None, {}))
def fake_resolve_endpoint(kind, owner=None):
captured.setdefault("resolve_calls", []).append((kind, owner))
return None, None, {}
monkeypatch.setattr(endpoint_resolver, "resolve_endpoint", fake_resolve_endpoint)
monkeypatch.setattr(llm_core, "llm_call_async", fake_llm_call_async)
monkeypatch.setattr(model_context, "estimate_tokens", lambda messages: 100)
monkeypatch.setattr(model_context, "get_context_length", lambda endpoint_url, model: 1000)
@@ -146,7 +151,11 @@ def _registered_compact_response(monkeypatch, history, active_run=False):
import src.llm_core as llm_core
monkeypatch.setattr(agent_runs, "is_active", lambda session_id: active_run)
monkeypatch.setattr(endpoint_resolver, "resolve_endpoint", lambda kind, owner=None: (None, None, {}))
def fake_resolve_endpoint(kind, owner=None):
captured.setdefault("resolve_calls", []).append((kind, owner))
return None, None, {}
monkeypatch.setattr(endpoint_resolver, "resolve_endpoint", fake_resolve_endpoint)
monkeypatch.setattr(llm_core, "llm_call_async", fake_llm_call_async)
session = _FakeSession(history)
@@ -212,6 +221,24 @@ def test_registered_manual_compact_route_tolerates_none_content(monkeypatch):
assert manager.replaced_messages is not None
def test_registered_manual_compact_route_uses_session_owner(monkeypatch):
response, captured, manager = _registered_compact_response(
monkeypatch,
[
ChatMessage(role="user", content="start"),
ChatMessage(role="assistant", content="tool call"),
ChatMessage(role="tool", content="tool result"),
ChatMessage(role="assistant", content="done"),
ChatMessage(role="user", content="next"),
ChatMessage(role="assistant", content="final"),
],
)
assert response.status_code == 200
assert manager.replaced_messages is not None
assert ("utility", "session-owner") in captured["resolve_calls"]
def test_registered_manual_compact_route_rejects_active_agent_run(monkeypatch):
response, captured, manager = _registered_compact_response(
monkeypatch,
+1 -1
View File
@@ -365,7 +365,7 @@ async def test_build_chat_context_incognito_does_not_duplicate_current_user_mess
def fake_add_user_message(sess, chat_handler, preprocessed, incognito=False):
sess.messages.append({"role": "user", "content": preprocessed.user_content})
async def fake_maybe_compact(sess, endpoint_url, model, messages, headers):
async def fake_maybe_compact(sess, endpoint_url, model, messages, headers, owner=None):
return messages, 123, False
monkeypatch.setattr(chat_helpers, "preprocess", fake_preprocess)