Commit Graph
3 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 5 6eda7d706e feat(web-app-openwebui): offer the personal agents to their agent-user group only
With svc-ai-agent-broker in the inventory, Open WebUI adds the broker as a
second OpenAI connection (OPENAI_API_BASE_URLS/KEYS). It forwards the
user's id and email headers and waits up to the broker's request_timeout,
so a cold agent start can finish.

BYPASS_MODEL_ACCESS_CONTROL turns false while the broker is enabled.
agent_model_grants.py then runs inside the container and:
- grants every LiteLLM model read access for all users;
- grants hermes and openclaw read access for their
  /roles/web-app-<platform>/agent-user group only;
- reads grants back from the database, because the API view is filtered by
  the admin access control.

The grant rows also give every gateway model the database entry whose
absence made check_model_access answer "Model not found".

Open WebUI shows a model without a grant to administrators only. So a
svc-ai-litellm deploy now reruns the grant step in a running Open WebUI,
and a model added to the gateway is visible without an Open WebUI deploy.
The include sets application_id explicitly, because the app loader's
include var would otherwise resolve Open WebUI's lookups against LiteLLM.

Without the broker the rendered connection, key and bypass are unchanged;
only the variable name moves to the plural form Open WebUI falls back from.

The Playwright spec:
- revokes biber in Keycloak;
- removes the stale Open WebUI membership, which the OIDC sync keeps when
  the groups claim turns empty;
- asserts every served gateway model is listed for biber and no agent
  model is;
- grants the groups and expects both agents to answer through the broker.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 19:27:49 +02:00
kevinveenbirkenbachandClaude Opus 5 60a9dca2b1 test(openwebui): make the MCP client open the connection, not just record it
GET /api/v1/tools/ builds its answer for an MCP server out of stored config alone - routers/tools.py:125-126 reads info off the connection row and never touches the network. So the three specs that read it pass unchanged against a provider that is down, refuses the deployed bearer, or serves nothing. What they establish is registration and the OIDC group to access-grant chain, not that the bridge carries.

POST /api/v1/configs/tool_servers/verify is the one route in 0.11.0 that does a real MCP initialize plus tools/list without an LLM, and it authenticates with the key in the body it is handed. The spec reads the stored connection and echoes it back, so the handshake runs over the deployed URL with the deployed credential through Open WebUI's own client, and asserts three things per server: it answers, it serves at least one tool, and the tool set contains the read probe its meta/mcp.yml declares.

Only bearer connections are verified. A system_oauth one resolves the caller's own upstream token per request and the administrator holds none, so it would fail on the caller rather than on the bridge; no provider declares auth: oidc today, but the filter keeps the spec honest when one does. The per-test budget scales with the number of registered servers instead of a fixed guess.

MCP_EXPECTED_TOOLS carries the declared read probe per server. Its rendering is unit-tested by extracting the block from the template itself rather than restating it, and decoded the way a spec decodes it - dotenv_quote emits an escaped JSON string that decodeDotenvQuotedValue unwraps before the spec parses the payload, so comparing after one decode would test a value no spec ever sees.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 21:58:54 +02:00
kevinveenbirkenbachandClaude Opus 5 493fedc6c6 refactor(tests): move the role unit mirrors under tests/unit/python
The role scripts moved into per-language folders but their unit mirrors did
not follow, so the index carried paths under tests/unit/roles/ that no longer
existed on disk while their counterparts under tests/unit/python/roles/ were
untracked.

That stale index broke the gate while naming the wrong culprit: the
repository lint reports "rsync --backup in 19 place(s)" because it counts
files it cannot open, and every entry was an [Errno 2] No such file or
directory rather than an actual rsync call.

Fifteen of the moves are recognised as renames; the rest are __init__ files
that collapsed into the new tree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 12:33:48 +02:00