From dc06bf558e0e475fa6601c38a48106746a659d82 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Wed, 5 Aug 2026 16:49:04 -0300 Subject: [PATCH] fix(muse-spark-web): document the ecto1: WS auth token requirement in credential hint, spec, and error message (#9502) Closes #9502 --- .../fixes/9502-muse-ecto1-auth-token.md | 1 + docs/reference/PROVIDER_REFERENCE.md | 11 ++-- open-sse/executors/muse-spark-web.ts | 2 +- src/lib/providers/validation/webProvidersB.ts | 4 +- src/shared/constants/providers/web-cookie.ts | 5 +- src/shared/providers/webSessionCredentials.ts | 10 ++- .../unit/muse-spark-cookie-copy-5449.test.ts | 7 ++- .../muse-spark-ws-auth-token-9502.test.ts | 62 +++++++++++++++++++ 8 files changed, 88 insertions(+), 14 deletions(-) create mode 100644 changelog.d/fixes/9502-muse-ecto1-auth-token.md create mode 100644 tests/unit/muse-spark-ws-auth-token-9502.test.ts diff --git a/changelog.d/fixes/9502-muse-ecto1-auth-token.md b/changelog.d/fixes/9502-muse-ecto1-auth-token.md new file mode 100644 index 0000000000..5962deb307 --- /dev/null +++ b/changelog.d/fixes/9502-muse-ecto1-auth-token.md @@ -0,0 +1 @@ +- fix(muse-spark-web): document the ecto1: WS auth token requirement in the credential hint, spec, and error message (#9502) diff --git a/docs/reference/PROVIDER_REFERENCE.md b/docs/reference/PROVIDER_REFERENCE.md index f8b776e976..a12b447126 100644 --- a/docs/reference/PROVIDER_REFERENCE.md +++ b/docs/reference/PROVIDER_REFERENCE.md @@ -1,16 +1,16 @@ --- title: "Provider Reference" version: 3.8.50 -lastUpdated: 2026-07-30 +lastUpdated: 2026-08-05 --- # Provider Reference > **Auto-generated** from `src/shared/constants/providers.ts` — do not edit by hand. > Regenerate with: `npm run gen:provider-reference` -> **Last generated:** 2026-07-30 +> **Last generated:** 2026-08-05 -Total providers: **290**. See category breakdown below. +Total providers: **291**. See category breakdown below. ## Categories @@ -84,7 +84,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each | `kimi-web` | `kimi-web` | Kimi Web | Web cookie | [link](https://www.kimi.com/code?aff=omniroute) | Paste access_token from www.kimi.com DevTools → Application → Local Storage. A legacy kimi-auth cookie is also accepted. | — | | `lmarena` | `lma` | Arena (Free) | Web cookie | [link](https://arena.ai) | Paste the full Cookie header from arena.ai (DevTools → Network → request → Cookie). Include arena-auth-prod-v1.0/.1… and cf_clearance/__cf_bm when present. OmniRoute uses Chrome TLS impersonation; if Arena still 403s, set providerSpecificData.recaptchaV3Token from a live browser session. | — | | `microsoft-designer-web` | `msdesigner` | Microsoft Designer (Image Generation) | Web cookie | [link](https://designer.microsoft.com) | Sign in at designer.microsoft.com, then open DevTools → Network, generate an image, and find the request to DallE.ashx?action=GetDallEImagesCogSci. Copy the value of its Authorization: Bearer header (the access_token — no 'Bearer ' prefix). The token is short-lived; this is an unofficial, reverse-engineered integration. | — | -| `muse-spark-web` | `ms-web` | Muse Spark Web (Meta AI) | Web cookie | [link](https://www.meta.ai) | Paste your ecto_1_sess value or full cookie header from meta.ai | emulated | +| `muse-spark-web` | `ms-web` | Muse Spark Web (Meta AI) | Web cookie | [link](https://www.meta.ai) | Paste your ecto_1_sess cookie AND the ecto1:... WS auth token from meta.ai. Capture the ecto1: token in DevTools → Network → WS → the clippy request's Authorization query param. Example: ecto_1_sess=4240a308...NVDg0; ecto1:ABCD... | emulated | | `notion-web` | `nw` | Notion AI Web (Unofficial/Experimental) | Web cookie | [link](https://www.notion.so) | Paste only the token_v2 cookie VALUE from app.notion.com (DevTools → Application → Cookies → token_v2). Do not paste token_v2= or the full Cookie header. Workspace is auto-detected; space_id / notion_user_id are optional. | — | | `perplexity-web` | `pplx-web` | Perplexity Web (Pro/Max) | Web cookie | [link](https://www.perplexity.ai) | Paste your __Secure-next-auth.session-token cookie value from perplexity.ai | emulated | | `poe-web` | `poe` | Poe Web (Subscription) | Web cookie | [link](https://poe.com) | Paste your p-b cookie value from poe.com (DevTools → Application → Cookies → p-b) | — | @@ -97,7 +97,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each | `zai-web` | `zw` | Z.ai Web (Free) | Web cookie | [link](https://chat.z.ai) | Paste the full Cookie header from chat.z.ai (must include the token= cookie) | — | | `zenmux-free` | `zmf` | ZenMux Free (Web) | Web cookie | [link](https://zenmux.ai) | Login at zenmux.ai, then export all cookies using EditThisCookie or Cookie-Editor and paste the full Cookie header string here. Refresh every ~30 days. | — | -## API Key Providers (paid / paid-with-free-credits) (195) +## API Key Providers (paid / paid-with-free-credits) (196) | ID | Alias | Name | Tags | Website | Notes | |----|-------|------|------|---------|-------| @@ -130,6 +130,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each | `bytez` | `bytez` | Bytez | API key | [link](https://bytez.com) | $1 free credits, refreshes every 4 weeks | | `cerebras` | `cerebras` | Cerebras | API key | [link](https://inference.cerebras.ai) | Free Trial: 1M tokens/day, 30K TPM, 5 RPM — no credit card. | | `charm-hyper` | `charm-hyper` | Charm Hyper | API key | [link](https://hyper.charm.land) | 100 free monthly Hypercredits on signup | +| `cheaperinference` | `cinf` | Cheaper Inference | API key | [link](https://cheaperinference.com/?utm_source=omniroute) | — | | `chenzk` | `chenzk` | Chenzk API | API key | [link](https://chenzk.top) | — | | `chutes` | `chutes` | Chutes.ai | API key, aggregator | [link](https://chutes.ai) | Bearer API key for the Chutes OpenAI-compatible gateway. | | `clarifai` | `clarifai` | Clarifai | API key, enterprise | [link](https://docs.clarifai.com) | Use your Clarifai PAT or app-specific API key. OmniRoute targets the OpenAI-compatible endpoint at https://api.clarifai.com/v2/ext/openai/v1 and authenticates with Authorization: Key . | diff --git a/open-sse/executors/muse-spark-web.ts b/open-sse/executors/muse-spark-web.ts index 66a2e819ef..f93191c312 100644 --- a/open-sse/executors/muse-spark-web.ts +++ b/open-sse/executors/muse-spark-web.ts @@ -1287,7 +1287,7 @@ export class MuseSparkWebExecutor extends BaseExecutor { if (!authorization) { return errorResult( 400, - "Missing Authorization for Meta AI WebSocket — your cookie must include an ecto1:... auth token.", + "Missing Authorization for Meta AI WebSocket — paste the ecto1:... WS auth token from meta.ai DevTools (Network → WS → clippy request Authorization param), alongside your ecto_1_sess cookie.", "missing_authorization", {}, body diff --git a/src/lib/providers/validation/webProvidersB.ts b/src/lib/providers/validation/webProvidersB.ts index eb9a18096d..ae7f0810cc 100644 --- a/src/lib/providers/validation/webProvidersB.ts +++ b/src/lib/providers/validation/webProvidersB.ts @@ -50,14 +50,14 @@ export async function validateMuseSparkWebProvider({ apiKey, providerSpecificDat if (response.status === 401 || response.status === 403) { return { valid: false, - error: "Invalid Meta AI session cookie — re-paste abra_sess from meta.ai", + error: "Invalid Meta AI session cookie — re-paste ecto_1_sess from meta.ai", }; } if (/authentication required to send messages|login is required|sign in/i.test(responseText)) { return { valid: false, - error: "Invalid Meta AI session cookie — re-paste abra_sess from meta.ai", + error: "Invalid Meta AI session cookie — re-paste ecto_1_sess from meta.ai", }; } diff --git a/src/shared/constants/providers/web-cookie.ts b/src/shared/constants/providers/web-cookie.ts index 53aca4a611..84ccd25061 100644 --- a/src/shared/constants/providers/web-cookie.ts +++ b/src/shared/constants/providers/web-cookie.ts @@ -81,7 +81,10 @@ export const WEB_COOKIE_PROVIDERS = { website: "https://www.meta.ai", hasFree: true, freeNote: "Free with login — Meta AI platform with Llama models.", - authHint: "Paste your ecto_1_sess value or full cookie header from meta.ai", + authHint: + "Paste your ecto_1_sess cookie AND the ecto1:... WS auth token from meta.ai. " + + "Capture the ecto1: token in DevTools → Network → WS → the clippy request's Authorization query param. " + + "Example: ecto_1_sess=4240a308...NVDg0; ecto1:ABCD...", toolCalling: "emulated", }, "claude-web": { diff --git a/src/shared/providers/webSessionCredentials.ts b/src/shared/providers/webSessionCredentials.ts index 648c51d039..5c89d7cab1 100644 --- a/src/shared/providers/webSessionCredentials.ts +++ b/src/shared/providers/webSessionCredentials.ts @@ -98,10 +98,14 @@ export const WEB_SESSION_CREDENTIAL_REQUIREMENTS = { }, "muse-spark-web": { kind: "cookie", - credentialName: "abra_sess", - placeholder: "abra_sess=...; other=value", + // #9502: the WS protocol (#7528) needs both the ecto_1_sess cookie (GraphQL + // warmup/mode-switch) and a separate ecto1:... WS auth token (Authorization + // query param on wss://gateway.meta.ai/ws/clippy). The executor extracts the + // ecto1: token from the apiKey field via /ecto1:[^\s;]+/i. + credentialName: "ecto_1_sess + ecto1: WS auth token", + placeholder: "ecto_1_sess=...; ecto1:... (WS auth token from meta.ai DevTools → Network → WS → clippy)", acceptsFullCookieHeader: true, - storageKeys: ["cookie", "abra_sess"], + storageKeys: ["cookie", "ecto_1_sess", "abra_sess"], }, "hailuo-web": { kind: "token", diff --git a/tests/unit/muse-spark-cookie-copy-5449.test.ts b/tests/unit/muse-spark-cookie-copy-5449.test.ts index 825b4f1954..9d32176496 100644 --- a/tests/unit/muse-spark-cookie-copy-5449.test.ts +++ b/tests/unit/muse-spark-cookie-copy-5449.test.ts @@ -19,12 +19,15 @@ const webCookie = readFileSync( const executor = readFileSync(join(root, "open-sse", "executors", "muse-spark-web.ts"), "utf8"); test("provider form hint points at the live ecto_1_sess cookie, not retired abra_sess", () => { + // #9502: the hint now names BOTH the ecto_1_sess cookie and the ecto1: WS auth + // token; the live-cookie-name guard (ecto_1_sess present, retired abra_sess + // absent) still holds. assert.ok( - webCookie.includes("Paste your ecto_1_sess value"), + webCookie.includes("ecto_1_sess"), "muse-spark authHint must name ecto_1_sess" ); assert.ok( - !webCookie.includes("Paste your abra_sess"), + !/Paste your abra_sess/.test(webCookie), "muse-spark authHint must not name the retired abra_sess cookie" ); }); diff --git a/tests/unit/muse-spark-ws-auth-token-9502.test.ts b/tests/unit/muse-spark-ws-auth-token-9502.test.ts new file mode 100644 index 0000000000..6e0e61e0c4 --- /dev/null +++ b/tests/unit/muse-spark-ws-auth-token-9502.test.ts @@ -0,0 +1,62 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, join } from "node:path"; +import { MuseSparkWebExecutor } from "../../open-sse/executors/muse-spark-web.ts"; + +// #9502: the WS migration (#7528) requires a separate ecto1:... auth token the +// guidance never mentions, so a cookie-only credential (the documented input) +// always fails with 400 "Missing Authorization". + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const root = join(__dirname, "..", ".."); + +const webCookie = readFileSync( + join(root, "src", "shared", "constants", "providers", "web-cookie.ts"), + "utf8" +); +const webSessionCredentials = readFileSync( + join(root, "src", "shared", "providers", "webSessionCredentials.ts"), + "utf8" +); +const executor = readFileSync(join(root, "open-sse", "executors", "muse-spark-web.ts"), "utf8"); + +test("#9502: provider authHint mentions the ecto1: WS auth token, not only the ecto_1_sess cookie", () => { + // Extract the muse-spark-web block from the first `"muse-spark-web": {` (the + // key declaration, not the `id:` value) up to the next top-level provider key. + const startIdx = webCookie.indexOf('"muse-spark-web": {'); + assert.ok(startIdx >= 0, "muse-spark-web block not found"); + const museSection = webCookie.slice(startIdx, webCookie.indexOf('"claude-web"', startIdx)); + assert.match(museSection, /ecto1/, "authHint must mention the ecto1: WS auth token"); +}); + +test("#9502: web-session credential spec for muse-spark-web mentions the ecto1: WS auth token", () => { + const startIdx = webSessionCredentials.indexOf('"muse-spark-web": {'); + assert.ok(startIdx >= 0, "muse-spark-web credential spec not found"); + const museSection = webSessionCredentials.slice( + startIdx, + webSessionCredentials.indexOf('"hailuo-web"', startIdx) + ); + assert.match(museSection, /ecto1/, "credential spec must mention the ecto1: WS auth token"); +}); + +test("#9502: the Missing Authorization error message guides the user to the ecto1: token", () => { + assert.ok(/Missing Authorization.*ecto1:/.test(executor), "missing-auth error must name the ecto1: token"); +}); + +test("#9502: a cookie-only credential (no ecto1: token) is rejected with 400 — the actual user failure", async () => { + const exec = new MuseSparkWebExecutor(); + const result = await exec.execute({ + model: "muse-spark", + body: { messages: [{ role: "user", content: "hello" }] }, + stream: false, + credentials: { apiKey: "ecto_1_sess=4240a308abcdefNVDg0", connectionId: "conn-9502" }, + signal: null, + log: null, + upstreamExtraHeaders: undefined, + } as Parameters[0]); + assert.equal(result.response.status, 400, "cookie-only credential is rejected"); + const body = await result.response.json(); + assert.match(body.error.message, /Missing Authorization for Meta AI WebSocket/); +});