Skip to content

muse-proxy: direct mode — exchange the Muse CLI account token for the minted plan key and forward chat completions verbatim #2753

Closed

Description

@dezren39

What

Add a direct path to bin/muse-proxy.mjs so a chat completion no longer has to shell out to muse exec --json / a warm muse serve MSP host per request (which pays ~26k tokens of CLI harness scaffolding per call and flattens messages, losing native tool calling).

Requested directly by Drew on 2026-10-30 in the Muse side chat ("make the proxy do that", referring to the Muse CLI token-exchange mechanism).

Verified mechanism (live on amd-6, 2026-10-30)

  • The Muse CLI OAuth login token is an account token (dca:…, ~/.config/muse/auth.json → providers.meta.access_token, device_code). It is NOT an inference credential: direct /v1 calls with it return 401.
  • Exchange: POST https://api.meta.ai/muse-code/key with Authorization: Bearer <dca token>, x-api-version: 1.0.0, body {"onboard": false} → 200. Response contains api_key (the LLM|… Muse Code plan key), is_subs_active: true, require_payment: false, subs_tier_name, subs_usage, etc.
  • With the minted key: GET /v1/models → 200 (muse-spark-1.3, muse-wonder-1.0, -contributor variants). Chat completions reach the plan quota gate (429 Subscription quota exhausted when the 5h window is full — auth accepted).

Scope

  • Credential: read $MUSE_AUTH_PATH || ~/.config/muse/auth.json; use providers.meta.access_token for the exchange; if only providers.meta.api_key is present, use it directly. Never log secret values.
  • Exchange + in-memory cache (10 min TTL); on upstream 401 invalidate + re-exchange once + retry once. is_subs_active === false / require_payment === true = hard error, no silent fallback to CLI paths.
  • Forward POST /v1/chat/completions verbatim (messages/tools/tool_choice/response_format preserved; no formatPrompt flattening) to {API_BASE}/v1/chat/completions with the minted key; base model id via existing resolveModelAndEffort, reasoning_effort passed when explicitly requested; stream = untouched SSE passthrough; non-stream = upstream status+JSON verbatim. ANY upstream HTTP response (402/429/400) passes through as-is — never fall back after Meta has answered, never retry a quota wall.
  • Fall back to existing warm/exec paths ONLY when direct is unavailable before any upstream response (no credential, exchange transport failure, upstream fetch throws).
  • Kill switch MUSE_PROXY_DIRECT=0; API base override MUSE_PROXY_META_API_BASE (default https://api.meta.ai) for tests. /health gains a direct section (enabled, credential found, minted cached, tier/active — no secrets). /v1/models keeps the existing synthetic catalog.
  • Tests in bin/muse-proxy.test.mjs against a local stub Meta API: exchange shape + minted-key auth, exchange caching, streaming tool_calls passthrough, 429 passthrough with NO exec fallback, and MUSE_PROXY_DIRECT=0 / no-auth.json leaving exec behavior unchanged. All pre-existing tests still pass (node --test bin/muse-proxy.test.mjs on the laptop).

Related

Capability only: this does NOT re-enable the OpenCode muse provider (disabled by Drew 2026-10-25) or any proxy service.

Activity

  1. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    Final mechanism update (2026-10-30, supersedes the "forward chat completions verbatim" scope above)

    Live re-verification on the laptop today settled the upstream shape — attestation is per-route, not per-client:

    • With the minted key, POST /v1/chat/completions returns 403 client_attestation_denied no matter what headers are sent (including the real CLI's full header set).
    • POST /v1/responses returns 200 with bare Authorization only — no x-tbh-* headers, no special User-Agent, plain or -contributor model ids both work. One one-word Responses prompt cost 13 input tokens (vs ~26k through the CLI harness).

    So direct mode is implemented as a translation layer, not a verbatim chat forward:

    • POST /v1/chat/completions → translate to upstream POST /v1/responses (system → instructions; messages/tools/tool_choice mapped to Responses shapes; max_tokens → max_output_tokens; reasoning effort mapped, max/ultra → xhigh since Responses tops out there), and translate the Responses result (non-stream and SSE) back to chat-completion shape.
    • POST /v1/responses → verbatim passthrough with only the Authorization header swapped to the minted key.
    • Do NOT send impersonating tbh/muse-build headers — proven unnecessary.
    • Unchanged from the original scope: exchange via POST /muse-code/key ({"onboard": false}, x-api-version: 1.0.0), 10-min in-memory key cache, 401 → re-exchange + retry once, is_subs_active === false / require_payment === true → hard 502 with no CLI fallback, ANY upstream HTTP response passes through as-is (never fall back after Meta has answered), fallback to warm/exec only when direct never got an upstream response, MUSE_PROXY_DIRECT=0 kill switch, /health gains a direct section with no secret material.

    Implementation in progress on branch muse-proxy-direct-mode.

  2. added 2 commits that reference this issue on Oct 30, 2026
  3. added 3 commits that reference this issue on Oct 30, 2026
  4. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    Phase 2 complete — muse-direct is wired up and is now the only Meta door (2026-10-30)

    Pool identity (for gate/registry purposes): muse-direct draws the meta pool — the Muse Code High Usage plan — the same pool the retired meta and muse doors drew. It is not a separate pool. The fleet's quota gate should treat muse-direct sessions as Meta-pool burn. The named meta quota read is unaffected by the disable (it bypasses the disabled_providers filter and reads the Muse CLI auth store): verified live after the change — GET /api/quota/meta returns the live plan state (5h window 3% used, weekly 59% used, plan "Muse Code High Usage").

    What landed

    The attestation gate is also per-tool-name (new finding)

    The first throwaway-serve verification failed 403 client_attestation_denied despite the Responses route: bisecting a real request showed the gate also rejects function tools named bash/shell (probe-verified blocked set: bash, shell, apply_patch, search, read_file, write_file, edit_file; instructions, headers, and all other tool names pass). The proxy now aliases shell→run_command, bash→terminal on both direct paths and translates the model's calls back (#2764 chat path, #2766 passthrough); a still-denied chat request falls back to the CLI door. Verified after the fix: a throwaway-serve session on muse-direct/muse-spark-1.3-contributor (variant low) answered ok with finish stop — 17,503 input tokens, which is OpenCode's own build-agent prompt (system + tool definitions); a bare request through the same door costs 10–13 input tokens, vs ~26k via the CLI harness.

    Deployment state (laptop)

    • muse-proxy.service (user unit) runs the merged code on 127.0.0.1:18747; /health direct: enabled, credentialFound, mintedKeyCached, tier "Muse Code High Usage", subsActive.
    • The deployed OpenCode config (hardlinked to the repo file) carries the final config. The standing OpenCode/OpenChamber server was NOT restarted — muse-direct goes live there at its next natural respawn, and sessions pinned to meta/*/muse/* pick up the retargeted routes at the same moment.
  5. added a commit that references this issue on Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions