Files
Fu-Jie_openwebui-extensions/.agent/learnings/openwebui-mock-request.md
fujie f5a983fb4a feat(github-copilot-sdk): release v0.10.0 with native prompt restoration and live todo widget
- Restore native Copilot CLI prompts for authentic Plan Mode behavior
- Add SQLite-backed session management for state persistence via system prompt
- Implement Adaptive Autonomy (Agent chooses planning vs direct execution)
- Fix OpenWebUI custom tool context injection for v0.8.x compatibility
- Add compact Live TODO widget synchronized with session.db
- Upgrade SDK to github-copilot-sdk==0.1.30
- Remove legacy mode switch RPC calls (moved to prompt-driven orchestration)
- Fix intent status localization and widget whitespace optimization
- Sync bilingual READMEs and all documentation mirrors to v0.10.0
2026-03-07 04:30:15 +08:00

4.1 KiB

Building a Valid Mock Request for OpenWebUI Pipes

Discovered: 2026-03-05

Context

OpenWebUI Pipes run as a Pipe plugin, not as a real HTTP request handler. When the Pipe needs to call OpenWebUI-internal APIs (like generate_chat_completion, get_tools, etc.) or load Tools that do the same, it must provide a fake-but-complete Request object.

Finding

OpenWebUI's internal functions expect request to satisfy several contracts:

request.app.state.MODELS     → dict { model_id: ModelModel }  — MUST be populated!
request.app.state.config     → config object with all env variables
request.app.state.TOOLS      → dict (can start empty)
request.app.state.FUNCTIONS  → dict (can start empty)
request.app.state.redis      → None is fine
request.app.state.TOOL_SERVERS → []  is fine
request.app.url_path_for(name, **path_params) → str
request.headers              → dict with Authorization, host, user-agent
request.state.user           → user dict
request.state.token.credentials → str (the Bearer token, without "Bearer " prefix)
await request.json()         → dict (the raw request body)
await request.body()         → bytes (the raw request body as JSON bytes)

Solution / Pattern

from types import SimpleNamespace
import json as _json_mod

def _build_openwebui_request(user: dict, token: str, body: dict = None):
    from open_webui.config import PERSISTENT_CONFIG_REGISTRY
    from open_webui.models.models import Models as _Models

    # 1. Build config from registry
    config = SimpleNamespace()
    for item in PERSISTENT_CONFIG_REGISTRY:
        val = item.value
        if hasattr(val, "value"):
            val = val.value
        setattr(config, item.env_name, val)

    # 2. Populate MODELS from DB — critical for model validation
    system_models = {}
    try:
        for m in _Models.get_all_models():
            system_models[m.id] = m
    except Exception:
        pass

    # 3. Build app_state
    app_state = SimpleNamespace(
        config=config,
        TOOLS={},
        TOOL_CONTENTS={},
        FUNCTIONS={},
        FUNCTION_CONTENTS={},
        MODELS=system_models,   # <-- KEY: must not be empty!
        redis=None,
        TOOL_SERVERS=[],
    )

    # 4. url_path_for helper
    def url_path_for(name: str, **params):
        if name == "get_file_content_by_id":
            return f"/api/v1/files/{params.get('id')}/content"
        return f"/mock/{name}"

    app = SimpleNamespace(state=app_state, url_path_for=url_path_for)

    # 5. Async body helpers
    async def _json():
        return body or {}

    async def _body_fn():
        return _json_mod.dumps(body or {}).encode("utf-8")

    # 6. Headers
    headers = {
        "user-agent": "Mozilla/5.0",
        "host": "localhost:8080",
        "accept": "*/*",
    }
    if token:
        headers["Authorization"] = token if token.startswith("Bearer ") else f"Bearer {token}"

    return SimpleNamespace(
        app=app,
        headers=headers,
        method="POST",
        cookies={},
        base_url="http://localhost:8080",
        url=SimpleNamespace(path="/api/chat/completions", base_url="http://localhost:8080"),
        state=SimpleNamespace(
            token=SimpleNamespace(credentials=token or ""),
            user=user or {},
        ),
        json=_json,
        body=_body_fn,
    )

Token Extraction

Tokens can be found in multiple places. Check in order:

# 1. Direct in body (some SDK requests embed it)
token = body.get("token")

# 2. In metadata
token = token or (metadata or {}).get("token")

# 3. In the original __request__ Authorization header
if not token and __request__ is not None:
    auth = getattr(__request__, "headers", {}).get("Authorization", "")
    if auth.startswith("Bearer "):
        token = auth.split(" ", 1)[1]

Gotchas

  • app.state.MODELS empty = "Model not found" for any model ID, even correct ones.
  • TOOL_SERVER_CONNECTIONS must be synced from DB, not from in-memory cache (stale in multi-worker).
  • request.state.token.credentials should be the raw token (no "Bearer " prefix).
  • Tools may call await request.json() — must be an async method, not a regular attribute.