Skip to content

feat: add hailuo-web (MiniMax web) chat provider (#6673)#7734

Open
diegosouzapw wants to merge 1 commit into
release/v3.8.49from
feat/6673-hailuoai-web-provider
Open

feat: add hailuo-web (MiniMax web) chat provider (#6673)#7734
diegosouzapw wants to merge 1 commit into
release/v3.8.49from
feat/6673-hailuoai-web-provider

Conversation

@diegosouzapw

Copy link
Copy Markdown
Owner

Summary

Adds hailuo-web as a new free, _token-based web-cookie chat provider targeting the MiniMax consumer chat product at hailuo.ai (chat.minimax.io) — distinct from the existing paid API-key minimax/minimax-cn providers. Follows the established kimi-web/doubao-web/qwen-web web-cookie pattern per CLAUDE.md → "Adding a New Provider".

Refs #6673

Protocol source

Ported directly from the g4f reference implementation (g4f/Provider/needs_auth/mini_max/{HailuoAI,crypt}.py), re-fetched and re-verified byte-for-byte during this implementation (not just the earlier triage summary):

  • Endpoint: POST https://www.hailuo.ai/v4/api/chat/msg?<fingerprint query>
  • Auth: token header — the _token value from the site's localStorage, plus a per-request yy signature header.
  • Signing: yy = MD5(urlencode(path_and_query) + "_" + bodyToYy + MD5(timestamp) + "ooui"), where bodyToYy = MD5(characterID) + MD5(msgContent) + MD5(chatID) + MD5(""). Ported with Node's built-in crypto.createHash("md5") — no new dependency, no eval.
  • Body: multipart form (characterID, msgContent, chatID, searchMode).
  • Response: event:/data: SSE lines — send_result (chat title + chatID, once) and message_result (a cumulative content snapshot per event, diffed into deltas here) until a close_chunk event ends the stream.

This resolves 3 of the 4 "unresolved unknowns" the triage plan flagged (exact API host, auth header shape, doc/API path) with concrete evidence from the g4f source rather than guesses. The 4th (exact model id list) turned out to be moot: the g4f provider exposes a single default_model with no model-selection query param in the request at all — so this ships one catalog model (hailuo) rather than a guessed MiniMax-M-series list.

Device-fingerprint fields (device_id, uuid, os/browser name, screen dims) are normally generated by the browser and stored in localStorage. When the user hasn't captured the real values, the executor derives stable per-connection ones from the token via MD5 (pure function — same token always yields the same fingerprint, no server-side state needed).

What was deferred / remains unverified

The signing scheme and SSE parsing are pure functions, fully unit-tested. What is not verified is the live upstream handshake:

  • Whether www.hailuo.ai is still the current live host and /v4/api/chat/msg the current live path (both taken from the actively-maintained g4f source, not from a fresh live capture).
  • Whether a synthetic/derived device_id/uuid is accepted by the upstream, or whether it strictly requires the browser-generated values from localStorage (the authHint tells users to capture _token; device fingerprint is best-effort/derived).
  • Whether any additional anti-bot header (e.g. a TLS/JA3 fingerprint check, similar to Arena/Grok Web) is now required beyond what g4f's aiohttp-based client sends.

Live check needed before this is fully verified: deploy to the VPS (root@192.168.0.15), configure a hailuo-web connection with a real _token captured from a logged-in hailuo.ai session (DevTools → Application → Local Storage), and send a real chat request end-to-end, e.g.:

curl -sS http://127.0.0.1:20128/v1/chat/completions \
  -H "Authorization: Bearer <omniroute-api-key>" \
  -H "Content-Type: application/json" \
  -d '{"model":"hailuo-web/hailuo","messages":[{"role":"user","content":"hi"}],"stream":false}'

If the live capture shows a different signing order, an additional required header, or a different device-fingerprint requirement, open-sse/executors/hailuo-web.ts needs a follow-up fix before this is treated as production-verified.

How validated (Hard Rule #18 — TDD)

New test file tests/unit/executor-hailuo-web.test.ts (17 cases), including:

  • getBodyToYy/generateYyHeader signature test vectors independently re-derived via Python's hashlib.md5 + urllib.parse.quote (not asserted against a guessed literal) — the exact commands are documented as comments in the test file.
  • pyQuote percent-encoding matches Python's quote(s, safe="") exactly (not encodeURIComponent, which leaves !*'() unescaped).
  • Stable/derived vs. user-supplied device fingerprint.
  • Text-only message folding + rejection of tool-calls/tool-result content.
  • Cumulative message_result.content → delta diffing.
  • SSE line parsing, including a malformed/truncated data: line that must not throw.
  • 401 (invalid/expired token) → terminal error; 429 → transient error (contrast pair, per RESILIENCE_GUIDE.md).
  • Streaming and non-streaming end-to-end dispatch against a mocked fetch.

Red → green proof: open-sse/executors/hailuo-web.ts does not exist on origin/release/v3.8.49 (git show origin/release/v3.8.49:open-sse/executors/hailuo-web.ts → "exists on disk, but not in ...") — the new test file's import(...) of that module would fail before this change, and all 17 cases pass after it:

node --import tsx/esm --test tests/unit/executor-hailuo-web.test.ts
# ℹ tests 17 / pass 17 / fail 0

Also extended tests/unit/provider-alias-uniqueness.test.ts (appended, not editing existing lines) to pin that hailuo-web resolves to its own id/alias and does not collide with minimax/minimax-cn.

Gates run (all green)

  • node scripts/check/check-test-discovery.mjs
  • npm run typecheck:core / npm run typecheck:noimplicit:core — clean on all touched files (pre-existing unrelated noimplicit errors in combo.ts/usageTracking.ts/cliRuntime.ts confirmed pre-existing on base via git show origin/release/v3.8.49:<path>)
  • npx eslint --suppressions-location config/quality/eslint-suppressions.json <changed files> — 0 errors
  • npm run check:complexity-ratchets — green (had to extract execute() into prepareMsgContent/buildSignedRequest/dispatch/buildStreamingResponse/buildNonStreamingResponse helpers to get under the 80-line/15-complexity/15-cognitive caps)
  • node scripts/check/check-file-size.mjs — OK, no frozen-file growth
  • npm run check:cycles — no new cycles
  • npm run check:provider-consistency — OK
  • Golden snapshot (tests/snapshots/provider/translate-path.json) regenerated via UPDATE_GOLDEN=1 node --import tsx/esm --test tests/unit/provider-translate-path-golden.test.ts — diff confirmed purely additive (only the new hailuo-web entry), then re-ran without UPDATE_GOLDEN to confirm green
  • docs/reference/PROVIDER_REFERENCE.md regenerated via npm run gen:provider-reference (auto-generated — not hand-edited); unrelated entries in the diff (kimi-coding rename, agnes/dahl/xai-oauth additions) are pre-existing catalog state the doc was simply one day stale on, not introduced by this PR
  • node scripts/check/check-fabricated-docs.mjs --strict — the only 3 failures are the pre-existing base-red claims called out in the task brief (being fixed separately in docs: fix three stale references failing the fabricated-docs gate #7728), unrelated to this change

Not run: npm run test:coverage (heaviest gate, shared box per project convention — CI runs it authoritatively).

i18n

Checked src/i18n/messages/en.json for a provider-label pattern (kimiWebLabel, doubaoWebLabel, etc.) — confirmed these keys are unused dead/legacy entries (no source references them anywhere), and most recent web-cookie providers (grok-web, deepseek-web, adapta-web, yuanbao-web, gemini-business, etc.) don't have entries either. No new i18n keys needed; the dashboard renders provider names straight from the catalog name field.

Not in scope for this PR

npm run check:docs-counts reports pre-existing drift (README.md/AGENTS.md/CLAUDE.md hardcode "265 providers", real count is now 269) — this drift predates this PR (base was already at 268 vs. the claimed 265) and updating those 3 files' prose isn't part of the "Adding a New Provider" recipe; left untouched as out-of-scope pre-existing debt.

Adds hailuo-web as a new free web-cookie chat provider targeting the
MiniMax consumer chat product at hailuo.ai (chat.minimax.io), distinct
from the existing paid API-key minimax/minimax-cn providers.

Ported from the g4f reference implementation
(g4f/Provider/needs_auth/mini_max/{HailuoAI,crypt}.py):
- MD5-chain request signing (generate_yy_header/get_body_to_yy)
- Custom event:/data: SSE parsing (send_result/message_result/close_chunk),
  where message_result.content is a cumulative snapshot diffed into deltas
- Device-fingerprint query params, derived deterministically per-connection
  from the token when the user hasn't captured the real browser values

New catalog entry, executor, registry entry, dispatch wiring, tests
(17 cases covering signing test vectors independently verified via
Python hashlib.md5, SSE parsing, streaming/non-streaming dispatch, and
401-terminal vs 429-transient error mapping), and a regenerated
provider-translate-path golden snapshot (purely additive diff).
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

}

function md5(input: string): string {
return createHash("md5").update(input, "utf8").digest("hex");
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