feat(ai): services partages A2 + SSE streaming B4 + confirmations UI B5 (#79)
CI / lint (push) Successful in 58s
CI / security (push) Successful in 40s
CI / test (push) Successful in 1m15s
CI / build (push) Successful in 37s
CI / e2e (push) Successful in 10m15s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s

This commit is contained in:
2026-09-11 17:06:40 -04:00
parent c55e3e0cbc
commit 4c4e415975
27 changed files with 1465 additions and 386 deletions
+62
View File
@@ -823,3 +823,65 @@ class TestBooksLMAgentEndpoint:
assert resp.status_code == 200
assert "event: confirmation" in resp.text
assert "_agent_write" in resp.text
def test_agent_confirmation_resume(self, bookslm_client, monkeypatch):
import re
import backend.bookslm_routes as routes
from backend.ai_chat import LLMResponse, ToolCall
from backend.tools import registry
from backend.tools.api import ToolRisk
from backend.tools.registry import ToolSpec
from backend.tools.schemas import ListVaultsInput
executed = {}
def handler(ctx, params):
executed["ran"] = True
return {"ok": True}
spec = ToolSpec(
name="_agent_write_resume",
description="write for tests",
input_model=ListVaultsInput,
handler=handler,
risk=ToolRisk.WRITE,
)
monkeypatch.setitem(registry._REGISTRY, "_agent_write_resume", spec)
responses = [LLMResponse(tool_calls=[ToolCall(id="c1", name="_agent_write_resume", arguments={})])]
async def fake_chat_completion(messages, **kwargs):
return responses.pop(0)
monkeypatch.setattr(routes, "chat_completion", fake_chat_completion)
monkeypatch.setattr(routes, "_resolve_provider_name", lambda requested: "deepseek")
token, _ = _login_bookslm(bookslm_client)
payload = {"vault": "TestVault", "directory": "", "message": "crée un fichier", "mode": "directory"}
resp = bookslm_client.post(
"/api/ai/bookslm/agent",
json=payload,
headers={"Authorization": f"Bearer {token}"},
)
assert "event: confirmation" in resp.text
assert "_agent_write_resume" in resp.text
assert executed.get("ran") is not True
match = re.search(r"event: confirmation\ndata: (.*)", resp.text)
assert match, resp.text
confirmation = json.loads(match.group(1))
assert confirmation["pending"]["error"]["tool"] == "_agent_write_resume"
# Resume: apply the pending call, then the model answers.
responses.append(LLMResponse(content="C'est fait."))
resume_payload = dict(payload, confirm=confirmation["pending"], confirm_messages=confirmation["messages"])
resp2 = bookslm_client.post(
"/api/ai/bookslm/agent",
json=resume_payload,
headers={"Authorization": f"Bearer {token}"},
)
assert resp2.status_code == 200
assert executed.get("ran") is True
assert "C'est fait." in resp2.text
assert "event: message" in resp2.text