Adds the Desktop-exclusive Preview models and the Xiaomi account-session
route to the existing xiaomi-mimo provider instead of a separate
xiaomi-desktop provider, so the dashboard shows one MiMo entry rather than
three overlapping ones.
Dual auth, same pattern as kimi — API key (sk-) covers the cloud API,
Desktop/OAuth adds the account session used by the Preview models:
- registry: category oauth, authModes [oauth, apikey], oauth block, the two
mimo-x-*-preview models, and the invite signupUrl
- executor: routes Preview models to the account-service route with a Cookie
session, everything else keeps the sourceFormat-matched transport
- oauth: custom ECDH encrypted-callback flow (X25519 -> SHA256 -> AES-256-GCM)
with a loopback callback proxy, plus one-click import of the local Desktop
auth.json
- usage: weekly quota from the account session
Fixes found while merging:
- the OAuth browser flow was dead: poll-status cleared the session before the
client could POST /exchange, so every exchange returned 400
- a Claude-format client was sent to /v1/chat/completions instead of the
declared /anthropic/v1/messages transport, because buildUrl ignored
runtimeTransport
- stopXiaomiMimoProxy leaked every pending session (each holding an X25519
private key) for the process lifetime
- the OAuth exchange did not persist the Desktop passToken, so the Preview
models could never work after a browser sign-in
Removes dead code: the local engine token minting (mimoEngine, never called
on the request path), the model-catalog and usage routes, engineToken/
engineUrl plumbing, and an unread top-level usage block.
Adds tests/unit/xiaomi-mimo-{executor,oauth-session,oauth-proxy}.test.js —
the provider previously had none.
100 lines
3.9 KiB
JavaScript
100 lines
3.9 KiB
JavaScript
import { DefaultExecutor } from "./default.js";
|
|
import { getMimoAccountCookie, invalidateMimoAccountCookieCache, MIMO_API_BASE, MIMO_API_UA } from "../shared/mimoAccount.js";
|
|
|
|
// Desktop-exclusive Preview models. These are served by the account service's
|
|
// /api/route proxy, authorized by the Xiaomi account session (NOT the sk- key).
|
|
// See shared/mimoAccount.js for the session handshake.
|
|
const PREVIEW_MODELS = new Set(["mimo-x-pro-preview", "mimo-x-flash-preview"]);
|
|
|
|
// Session cookie resolved in execute() (async) and read back by buildHeaders()
|
|
// (sync — BaseExecutor.execute does not await it). Carried on the per-request
|
|
// credentials object, same as runtimeTransport.
|
|
const COOKIE_KEY = "__mimoAccountCookie";
|
|
|
|
// Upstream calls may hand us either the bare id or a `provider/model` ref.
|
|
function bareModel(model) {
|
|
const s = String(model || "");
|
|
const i = s.indexOf("/");
|
|
return i >= 0 ? s.slice(i + 1) : s;
|
|
}
|
|
|
|
export class XiaomiMimoExecutor extends DefaultExecutor {
|
|
constructor() {
|
|
super("xiaomi-mimo");
|
|
}
|
|
|
|
static isPreviewModel(model) {
|
|
return PREVIEW_MODELS.has(bareModel(model));
|
|
}
|
|
|
|
buildUrl(model, stream, urlIndex = 0, credentials = null) {
|
|
// Preview models live on the account-service route, which is not one of the
|
|
// declared transports — resolve it before the default runtimeTransport path.
|
|
if (XiaomiMimoExecutor.isPreviewModel(model)) {
|
|
return `${MIMO_API_BASE}/api/route/chat/completions`;
|
|
}
|
|
// Cloud API models keep default handling, so a Claude-format client reaches
|
|
// the /anthropic/v1/messages transport.
|
|
return super.buildUrl(model, stream, urlIndex, credentials);
|
|
}
|
|
|
|
buildHeaders(credentials, stream = true, url, model) {
|
|
if (XiaomiMimoExecutor.isPreviewModel(model) && credentials?.[COOKIE_KEY]) {
|
|
// Preview models authenticate with the account-session cookie, not the key.
|
|
return {
|
|
"Content-Type": "application/json",
|
|
Accept: stream ? "text/event-stream" : "application/json",
|
|
"User-Agent": MIMO_API_UA,
|
|
Cookie: credentials[COOKIE_KEY],
|
|
};
|
|
}
|
|
return super.buildHeaders(credentials, stream, url, model);
|
|
}
|
|
|
|
transformRequest(model, body, stream, credentials) {
|
|
// super runs stripUnsupportedParams, which flattens Preview content-part
|
|
// arrays (see the xiaomi-mimo rule in translator/concerns/paramSupport.js).
|
|
const out = super.transformRequest(model, body, stream, credentials);
|
|
|
|
// Preview models: thinking/params get defaults only — never override what the
|
|
// caller set explicitly. (body.model is already `xiaomi/<id>` via upstreamModelId.)
|
|
if (XiaomiMimoExecutor.isPreviewModel(model)) {
|
|
if (out.thinking == null) out.thinking = { type: "enabled" };
|
|
if (out.temperature == null) out.temperature = 1.0;
|
|
if (out.top_p == null) out.top_p = 0.95;
|
|
if (!out.max_tokens) out.max_tokens = 4096;
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
async execute(args) {
|
|
const { model, credentials, proxyOptions = null } = args;
|
|
if (!XiaomiMimoExecutor.isPreviewModel(model)) return super.execute(args);
|
|
|
|
const cookie = await getMimoAccountCookie(credentials?.providerSpecificData, proxyOptions);
|
|
if (!cookie) {
|
|
throw new Error(
|
|
"Xiaomi MiMo account session unavailable. Sign in to MiMo Desktop once so its passToken is present, then retry.",
|
|
);
|
|
}
|
|
credentials[COOKIE_KEY] = cookie;
|
|
const result = await super.execute(args);
|
|
|
|
// A cached session can expire early — drop it and retry once with a fresh one.
|
|
if (result.response.status === 401) {
|
|
invalidateMimoAccountCookieCache();
|
|
const fresh = await getMimoAccountCookie(credentials?.providerSpecificData, proxyOptions).catch(() => null);
|
|
if (fresh) {
|
|
credentials[COOKIE_KEY] = fresh;
|
|
return super.execute(args);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
export const __test__ = { PREVIEW_MODELS, bareModel, COOKIE_KEY };
|
|
|
|
export default XiaomiMimoExecutor;
|