Repository navigation
feat(proxy,ui): add Moyai to the view switcher with quick connect - #45196
Conversation
Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
|
|
|
…tual key Adds POST /moyai/connect/start (admin only) minting a short-lived HMAC signed connect code, and POST /moyai/connect/exchange (public route) that verifies the code, mints a LiteLLM virtual key, and stores the moyai_url UI setting server side. The dashboard view switcher lists Moyai above Chat and navigates straight to a connected deployment, the /moyai page gains a quick connect dialog, a redirect state, and a connected success view, and UI Settings gets a Moyai disconnect row. Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
…cate error state The exchanged key is minted without an owning user so it cannot inherit admin rights, records the connecting admin in metadata, and is limited to openai_routes, anthropic_routes and /model/info. Exchange now fails early when the proxy has no database, gateway URL resolution is shared, and the Moyai landing relies on the dialog's own error region. Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
The useState initializer touched window.location during static prerender of /moyai, crashing the production build. Parse the callback params in an effect and render nothing until they are read. Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
PR overviewAll previously flagged issues have been addressed. No open security concerns remain on this pull request. Security reviewNo open security issues remain on this pull request. Fixed/addressed: 1 · PR risk: 0/10 |
…n moyai key alias lookup Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
… without setState in effect Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
…back Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
|
@greptile review |
| raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Only proxy admins can connect Moyai") | ||
|
|
||
| try: | ||
| moyai_url: Final = normalize_moyai_url(body.moyai_url) |
There was a problem hiding this comment.
If an admin connects a remote HTTP deployment, the browser sends the signed code without encryption. A network observer can redeem it first at /moyai/connect/exchange, receive a working gateway key, and block the intended connection. Require HTTPS for remote quick-connect URLs, with a separate localhost exception if needed
How this was verified: The browser sends code in an HTTP URL, and the public exchange returns an API key using that code without another credential
Rule Used: What: Fail any PR which may contains a security incident on litellm's authentication layer Why: Do not cause security incidents Bad: ```python # Check cache first cache_key = ( f"oidc_userinfo_{token[:20]}" # Use fi... (source)
| if os.environ.get("PROXY_BASE_URL"): | ||
| return os.environ["PROXY_BASE_URL"].rstrip("/") | ||
| return str(request.base_url).rstrip("/") |
There was a problem hiding this comment.
If PROXY_BASE_URL is set and the proxy runs under SERVER_ROOT_PATH, _gateway_url drops the path prefix. Moyai sends its exchange request to the wrong URL, so quick connect fails. The returned api_base also lacks the prefix. Use get_custom_url(str(request.base_url)), which already joins the public base URL and the current root path
Knowledge Base Used: Dashboard and enterprise UI
| db_existing: Final = await _ui_settings_db(UISettingsRepository(prisma_client)).find_unique( | ||
| where={"id": "ui_settings"} | ||
| ) |
There was a problem hiding this comment.
Connecting restores old settings
If a read replica is configured, _persist_moyai_url reads settings from it and then replaces the entire settings value on the writer. An admin's recent changes can disappear if the replica has not caught up. Read from the writer and make the merge safe against concurrent updates
| <a | ||
| href={uiHref("")} |
There was a problem hiding this comment.
Gateway button returns to plugin
After connecting from an enterprise plugin, “Back to AI Gateway” opens that plugin again because the link keeps the saved plugin mode. Users must use the switcher to reach the gateway. Call setMode("ai-gateway") before navigating, as ViewSwitcher does
Knowledge Base Used: Dashboard and enterprise UI
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| </div> | ||
| ); | ||
| } else { | ||
| content = <MoyaiLanding canQuickConnect={isProxyAdminRole(userRole ?? "")} onQuickConnect={onQuickConnect} />; |
There was a problem hiding this comment.
| connect_url: Final = f"{moyai_url}/connect/litellm?" + urlencode( | ||
| {"gateway_url": _gateway_url(request), "code": code, "return_to": body.return_to} | ||
| ) |
There was a problem hiding this comment.
Both URL validators accept queries and fragments, but connect_url appends its path to the raw URL. For https://moyai.example.com/#dashboard, the connection path and code stay in the fragment and never reach the server. An admin can pass the form checks but land at the wrong page. Reject queries and fragments for deployment base URLs, or build the URL from parsed parts
| return f"{_b64url(payload)}.{_b64url(signature)}" | ||
|
|
||
|
|
||
| def _decode_connect_code(master_key: str, code: str) -> dict: |
There was a problem hiding this comment.
Helper inputs lack required types
_decode_connect_code returns a bare dict, while _moyai_key_alias and _persist_moyai_url leave prisma_client untyped. AGENTS.md requires strongly typed parameters and forbids coarse types such as bare dict. Add the database type and a typed model for the decoded payload. This repository requirement must be satisfied before merging
Context Used: AGENTS.md (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| existing: dict = {} | ||
| db_existing: Final = await _ui_settings_db(UISettingsRepository(prisma_client)).find_unique( | ||
| where={"id": "ui_settings"} | ||
| ) | ||
| if db_existing and db_existing.ui_settings: | ||
| raw: Final = db_existing.ui_settings | ||
| existing = json.loads(raw) if isinstance(raw, str) else dict(raw) |
There was a problem hiding this comment.
Save helper reassigns settings
_persist_moyai_url initializes existing and later reassigns it. AGENTS.md forbids reassignment and requires every variable to use Final. Build existing once from the database row instead. This repository requirement must be satisfied before merging
Context Used: AGENTS.md (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
TLDR
Adds Moyai (Cloud Coding Agent) to the dashboard product switcher, above Chat. When no deployment is connected, it opens a
/moyailaunch page styled after the Moyai launch post, with GitHub and demo CTAs and a Quick connect button. An admin who already runs Moyai can quick connect it: the gateway issues a scoped virtual key to Moyai server to server, and from then on the switcher opens that deployment directly.User Flow
Before: the switcher had only AI Gateway and Chat
After: picking Moyai with nothing connected opens
/moyai. From there, Quick connect takes a Moyai URL, callsPOST /moyai/connect/start, and redirects to<moyai>/connect/litellm?gateway_url&code&return_to. The Moyai admin confirms. Moyai's server callsPOST /moyai/connect/exchange, gets a virtual key, checks it against/v1/models, and sends the user back to the "Moyai connected" view (workspace, key alias, model count). After that, picking Moyai goes straight to the deployment. UI Settings shows the connection and has a Disconnect buttonProtocol
The key only ever travels server to server and is never logged. All exchange failures return the same generic 400.
moyai_urlis a new allowlistedUISettingsfield, validated bynormalize_moyai_url(http/https only, host required, no credentials). Disconnect clearsmoyai_urlbut does not revoke the key. The Moyai side lives in a separate BerriAI/moyai branchThe five localStorage hooks now get
() => falseserver snapshots so/moyaiprerenders duringbuild-uiPre-Submission checklist
tests/unit/proxy/test_moyai_endpoints.py(including sequential, concurrent and cross-worker replay),test_proxy_setting_endpoints.py, and vitest for ViewSwitcher, MoyaiLanding, MoyaiConnected, moyaiConnect, UISettings/key/generaterejects the key with 403Screenshots / Proof
Link to Devin session: https://app.devin.ai/sessions/4b60e9642bca44a89c1fdf8050ef0467
Open in Devin Desktop: https://app.devin.ai/desktop/session/4b60e9642bca44a89c1fdf8050ef0467?variant=devin
Requested by: @ishaan-berri