mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-16 11:52:26 +03:00
MaxAI joins as a first-class signed provider: 13 chat models discovered live from /models/get_config plus 6 image models, routed through the standard /v1 endpoints with per-request X-Authorization signing, browserless onboarding, prompted tool-calling, vision input, image generation and document RAG. Reconciled on merge — worth reading, because the branch forked 227 commits back and 77 files conflicted. Only five carried MaxAI content; the rest was drift from the older release line and took the tip's side, taking the diff from 113 files to 37 (then 93 as counted against the current base). - executors/index.ts: the tip has since refactored the executor map to lazy dynamic imports, so MaxAI is registered in that shape rather than the branch's static import. - imageRegistry.ts: kept only the maxai block. The branch still carried microsoft-designer-web, which #11754 retired. - models/route.ts: the conflicting hunk was an unrelated Vertex/Anthropic URL change, not MaxAI — tip's side. - volcengine agent-plan/coding-plan registries: git auto-merged both sides and produced a duplicated supportsVision key, which TypeScript rejects (TS1117). Removed. One real integration break that only the combined state shows: the MaxAI entry declared no serviceKinds, which #11392 made required a few hours ago. Provider validation threw at load time and check:provider-consistency crashed outright. Declared ["llm"] — the image kinds derive from imageRegistry, per the convention in that PR's backfill. Every count was measured rather than taken from the branch, and each would have been wrong: reserved prefixes are 402, not the 397 the branch computed from its stale 395 base; providers are 353, not 354. PROVIDER_REFERENCE.md regenerated, the count updated across README/AGENTS.md/llm.txt and its 42 mirrors, package.json and 6 SVGs — every changed line in those files is a digit substitution and nothing else, verified by masking digits and comparing the removed and added sets (90 lines, identical). The executor-map golden snapshot was regenerated: keyCount 133 -> 134. The branch's file-size-baseline.json predates #12411's ratchet re-tightening, so it was discarded rather than merged — taking it would have silently undone that. The three files this PR grows (proxyFetch.ts +20 for the Windows/firefox_150 TLS profile, imageGeneration.ts +12, models/route.ts +48) were entered against the current baseline under one _rebaseline annotation; no other cap moves. Verified: typecheck:core clean, check:provider-consistency OK (269 REGISTRY entries, 353 canonical providers), check:docs-counts exit 0, check-file-size OK, check:cycles OK, and 79/79 across the MaxAI suites plus 21/21 reserved-prefix and 2/2 executor-map-golden. Thanks @arminanton — the provider work itself is thorough; it was the 227 commits of base that needed the attention.
97 lines
3.5 KiB
TypeScript
97 lines
3.5 KiB
TypeScript
/**
|
|
* MaxAI connection credential resolution.
|
|
*
|
|
* MaxAI's request signer needs three things bound together: the OpenAI-style
|
|
* `access_token` (Bearer, ~24h), the `device_id` that minted it (embedded in the
|
|
* signed `X-Authorization` — a mismatch is rejected), and the `user_id` (folded
|
|
* into the signature proof). OmniRoute stores these in the connection's
|
|
* `providerSpecificData` (minted by OmniRoute's own browser-mint flow — see
|
|
* maxaiBrowserLogin), so the router is self-contained and never reads any
|
|
* external (Hermes) token file.
|
|
*
|
|
* The access token is refreshed out-of-band by the browser-mint (the
|
|
* `/oauth/refresh_access_token` endpoint is deep-TLS-gated and cannot be called
|
|
* by any HTTP client — only a real browser passes), so this module only READS
|
|
* the stored credential; it does not attempt an HTTP refresh.
|
|
*/
|
|
|
|
export interface MaxaiCredential {
|
|
accessToken: string;
|
|
deviceId: string;
|
|
userId: string;
|
|
/** ~1-year refresh token used for browserless access-token refresh (optional). */
|
|
refreshToken?: string;
|
|
}
|
|
|
|
type ProviderSpecificData = Record<string, unknown> | null | undefined;
|
|
|
|
function firstString(...values: unknown[]): string | null {
|
|
for (const v of values) {
|
|
if (typeof v === "string") {
|
|
// Raw browser LocalStorage sometimes wraps the device id in quotes.
|
|
const trimmed = v.trim().replace(/^"|"$/g, "");
|
|
if (trimmed.length > 0) return trimmed;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** Decode the `user_id` from a MaxAI access JWT (subject.user_id or sub). No verify. */
|
|
export function userIdFromJwt(accessToken: string): string | null {
|
|
try {
|
|
const seg = accessToken.split(".")[1];
|
|
if (!seg) return null;
|
|
const b64 = seg.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - (seg.length % 4)) % 4);
|
|
const claims = JSON.parse(Buffer.from(b64, "base64").toString("utf8"));
|
|
const subject = claims?.subject as { user_id?: unknown } | undefined;
|
|
if (typeof subject?.user_id === "string") return subject.user_id;
|
|
if (typeof claims?.sub === "string") return claims.sub;
|
|
return null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/** Epoch seconds of the access-JWT `exp`, or 0 when undecodable. */
|
|
export function accessTokenExpiry(accessToken: string): number {
|
|
try {
|
|
const seg = accessToken.split(".")[1];
|
|
if (!seg) return 0;
|
|
const b64 = seg.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - (seg.length % 4)) % 4);
|
|
const claims = JSON.parse(Buffer.from(b64, "base64").toString("utf8"));
|
|
return typeof claims?.exp === "number" ? claims.exp : 0;
|
|
} catch {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resolve the MaxAI credential from a connection's providerSpecificData (with the
|
|
* OpenAI-style `access_token` optionally supplied separately by the caller, which
|
|
* is how OmniRoute threads the stored connection token). Returns null when not
|
|
* fully configured (all three of accessToken/deviceId/userId required).
|
|
*/
|
|
export function resolveMaxaiCredential(
|
|
psd: ProviderSpecificData,
|
|
accessTokenFromConnection?: string | null
|
|
): MaxaiCredential | null {
|
|
const accessToken = firstString(
|
|
accessTokenFromConnection,
|
|
psd?.maxaiAccessToken,
|
|
psd?.accessToken
|
|
);
|
|
if (!accessToken) return null;
|
|
|
|
const deviceId = firstString(psd?.maxaiDeviceId, psd?.deviceId);
|
|
if (!deviceId) return null;
|
|
|
|
const userId =
|
|
firstString(psd?.maxaiUserId, psd?.userId) ?? userIdFromJwt(accessToken);
|
|
if (!userId) return null;
|
|
|
|
const refreshToken =
|
|
firstString(psd?.maxaiRefreshToken, psd?.refreshToken) ?? undefined;
|
|
|
|
return { accessToken, deviceId, userId, refreshToken };
|
|
}
|