Skip to content

feat(oauth): Muse Code subscription device-flow login - #14267

Closed
caniko wants to merge 6 commits into
diegosouzapw:release/v3.8.52from
caniko:feat/muse-code-subscription-oauth
Closed

caniko wants to merge 6 commits into
diegosouzapw:release/v3.8.52from
caniko:feat/muse-code-subscription-oauth

Conversation

@caniko

@caniko caniko commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

Adds Muse Code subscription OAuth via the existing device-code framework.

Status: draft — implementation + unit/integration evidence complete, pending fresh CI + maintainer review

Device authorization, browser approval, key exchange, dashboard login, persisted reload, discovery config, route-level discovery, and gateway inference are demonstrated (see below). Remaining: fresh CI triage and upstream review.

What

  • New muse-code OAuth provider (src/lib/oauth/providers/muse-code.ts): RFC 8628 device authorization against auth.meta.com (bounded timeouts, no redirects, strict URL validation incl. embedded-credential rejection), server-side polling through the generic device tail with an allowlisted error-code set mapped to fixed messages (upstream descriptions always discarded, prototype-safe lookup), then a key exchange at api.meta.ai/muse-code/key (onboard: true) for the subscription inference key.
  • Inference authenticates with the exchanged subscription key (accessToken — the default executor prefers it for OAuth connections). The device account token authorizes the exchange and is discarded, never persisted. No refresh-token grant exists: reconnect replaces the stored key (dedup by persisted accountId, mirroring the Codex rule).
  • Registry: baseUrl https://api.meta.ai/v1 + /responses suffix (shared by OAuth and existing API-key connections), live modelsUrl for entitled Spark discovery, and static Spark entries matching the verified subscription contract (1.1/1.2/1.3, base + Contributor where entitled) tagged openai-responses so Chat callers translate correctly; live discovery stays authoritative so unentitled ids are rejected.
  • Canonical dual-auth metadata: the shared provider id resolves OAuth-first on all three paths (static catalog, getProviderById, merged index, alias lookup), tested; existing API-key connections untouched.
  • Public device client_id embedded via resolvePublicCred("muse_id") per docs/security/PUBLIC_CREDS.md (no literal in source).
  • Dashboard: OAuth catalog entry (subscription-risk notice + Contributor training note), device-code panel, wizard allowlist, dual-auth counting, connection-test config, icon mapping.
  • Errors from the key exchange never propagate response bodies (a success body carries the key itself).

Verification

  • Unit: device validation/poll/key-exchange/redaction/mapping, Spark targetFormat + discovery config + canonical entry (all resolution paths) + Chat-to-Responses dispatch (incl. second-turn tool-result continuity).
  • Integration (isolated SQLite, mocked Meta): persist→reload→restart→reconnect→disconnect with redaction; real /api/providers/[id]/models handler returns the mocked account catalog with the subscription bearer and excludes static-only ids.
  • Updated oauth-providers-config.test.ts, publicCreds.test.ts, translate-path golden (muse-code URLs resolve to the Responses endpoint).
  • Live Meta check with operator browser approval: device code issued, approved, poll + key exchange succeeded, exchanged key returned HTTP 200 from /v1/models including muse-spark-1.3-contributor. No secrets printed or persisted.
  • Live gateway demo (isolated data dir, this branch): dashboard device login approved by operator; connection row persisted as oauth with subscription key and accountId, no account token; gateway restart preserves and reloads it; minimal Chat Completions inference through the gateway returned HTTP 200 with correct model id, text output, and reasoning usage. Observation (pre-existing framework behavior, untouched): a 32-token budget starves on reasoning and the generic truncation exemption does not cover Responses-incomplete shape.
  • Focused suites green; typecheck:core clean; changed files lint/format clean. Streaming consumption relies on the generic Responses machinery plus the live non-streaming demo; SSE tool-continuation through the gateway remains operator-verified only via dashboard use, not an automated test.

Still open

⚠️ base-red inherited: #13866.

@caniko
caniko marked this pull request as draft September 20, 2026 16:16
@diegosouzapw

Copy link
Copy Markdown
Owner

The security hygiene here is genuinely the best of the four Muse Code PRs we're comparing —
origin-pinning the verification URL to auth.meta.com, rejecting embedded credentials, capping the
device-code expiry, and allowlisting the OAuth poll error codes so upstream free-text descriptions
can never reach logs or the UI. That last one is a pattern we'd like to adopt more widely.

What keeps it from being the landing candidate is the lifecycle: by design this branch discards the
Meta account token right after the login-time key exchange and never persists a refresh handle —
your own test at tests/unit/muse-code-oauth-persistence.test.ts asserts it ("no refresh grant
exists for this flow"). Meta's minted keys need re-minting on roughly a daily cadence (confirmed in
a live run on #13634), so connections created this way would go dead after about a day with no
automatic recovery, which is short of what #13415 asks for on expiration handling. We're landing
#13634, which implements the remint half and has been verified end to end against real Meta
endpoints.

I'd like to port your error-sanitization approach (origin pin + fixed poll-error allowlist) onto
that branch with a Co-authored-by trailer for you — let me know if you'd rather open it as its own
small PR instead. One minor note for either path: this provider isn't registered in
NO_PKCE_DEVICE_CODE_PROVIDERS the way the other three are; it's harmless today (the dispatcher
passes a codeVerifier that your pollToken ignores) but worth aligning. The API Route Typecheck
red you see is inherited from base-red #14496, not yours.

@diegosouzapw diegosouzapw changed the title feat(oauth): Muse Code subscription device-flow login [defer] feat(oauth): Muse Code subscription device-flow login Sep 28, 2026
@diegosouzapw diegosouzapw added the deferred-v3.8.52 Grande demais / suspeito para o lote atual; precisa de sessão dedicada no ciclo v3.8.52 label Sep 28, 2026
@diegosouzapw
diegosouzapw changed the base branch from release/v3.8.51 to release/v3.8.52 September 29, 2026 11:23
@diegosouzapw

Copy link
Copy Markdown
Owner

Re-homed to release/v3.8.52: v3.8.51 entered its release freeze, so the branch now belongs to the release captain and development continues on the next cycle. Nothing is wrong with this PR — it just needed a live base. No action needed from you; CI will re-run against the new base.

@caniko

caniko commented Sep 29, 2026

Copy link
Copy Markdown
Contributor Author

@diegosouzapw should I still do the earlier request?

@diegosouzapw diegosouzapw removed the deferred-v3.8.52 Grande demais / suspeito para o lote atual; precisa de sessão dedicada no ciclo v3.8.52 label Oct 1, 2026
@diegosouzapw diegosouzapw changed the title [defer] feat(oauth): Muse Code subscription device-flow login feat(oauth): Muse Code subscription device-flow login Oct 1, 2026
thinh0704hcm pushed a commit to thinh0704hcm/OmniRoute that referenced this pull request Oct 5, 2026
…iegosouzapw#14138, diegosouzapw#13634, diegosouzapw#14267, diegosouzapw#12687)

Single branch with everything useful from the open Muse Code PRs that
release/v3.8.52 does not have yet:

- diegosouzapw#15234: session-pinned multi-account round-robin, same-owner
  empty-response retry, semantic-cache bypass, Responses URL fix for
  custom baseUrl, 512 output-token floor.
- diegosouzapw#12687: opencode-go sends reasoning.effort=max verbatim for
  muse-spark-1.3+, one xhigh retry on the unsupported-effort 400.
- diegosouzapw#13634: MuseCodeExecutor restores the 24h prompt_cache_retention
  window stripped by the generic sanitizer.
- diegosouzapw#14267: device-flow hardening (origin pin, expiry cap, poll-error
  allowlist, bounded auth requests).
- diegosouzapw#14138: mint-failure contract (401/403 forces re-login,
  429/transient keeps the stored key).

Skipped as subsumed or rejected: duplicate OAuth flows (merged
diegosouzapw#14329 covers them), diegosouzapw#14492 parallel provider, llama catalog
removals, command-code/freebuff catalog additions.

Co-authored-by: mdc2122 <mdc2122@users.noreply.github.com>
Co-authored-by: SIGTERM-015 <SIGTERM-015@users.noreply.github.com>
Co-authored-by: KSonny4 <KSonny4@users.noreply.github.com>
Co-authored-by: caniko <caniko@users.noreply.github.com>
Co-authored-by: Chewji9875 <Chewji9875@users.noreply.github.com>
@caniko

caniko commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

Please review the complete PR candidate, not only the latest commit. Request recorded for head 3f9b0cc721a8443d7dd370c12817f1925e53658c against release/v3.8.52 at 666ea59b4c5980062c4012ee9228135ee4dd8989. Include any still-actionable historical finding.

@caniko

caniko commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

Closing this full-provider proposal as superseded. The maintainer selected a renewable lifecycle in #14267 (comment), and the live release/v3.8.52 at 989c8ca720173368fa71f31429467677dd11e26c already persists the DCA credential and re-mints inference keys (src/lib/oauth/providers/muse-code.ts, open-sse/services/tokenRefresh/providers/museCode.ts). Merging this candidate would duplicate/replace that supported implementation.

The original source head 3f9b0cc721a8443d7dd370c12817f1925e53658c is retained. The still-useful security-hardening port is explicitly tracked in #15869; it is not represented as implemented. Full historical feedback was audited and contains no Codex-authored finding. This closure is withdrawal of the superseded integration, not CI acceptance or acceptance of the remaining hardening.

@caniko caniko closed this Oct 8, 2026
diegosouzapw added a commit to kalpakprod/OmniRoute that referenced this pull request Oct 8, 2026
- oauth-polltoken-nonjson-body: the diegosouzapw#14267 hardening carried here
  allowlists Muse Code poll errors and never echoes upstream text, so a
  non-JSON 502 page now maps to a bare { error: "invalid_response" }
  instead of a description quoting the HTML. Kimi and GitHub keep the
  original assertion.
- opencode-go-muse-spark-1-3-12674: diegosouzapw#12687 adds `max` as an explicit
  muse-spark-1.3 alias (sent verbatim, xhigh fallback on the upstream
  unsupported-effort 400), so parseEffortLevel(-max) now resolves.

Co-authored-by: caniko <caniko@users.noreply.github.com>
Co-authored-by: Chewji9875 <Chewji9875@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants