mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-15 19:32:20 +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.
157 lines
5.4 KiB
TypeScript
157 lines
5.4 KiB
TypeScript
/**
|
|
* MaxAI signing-constants store + `ensure` gate.
|
|
*
|
|
* This is the persistence + freshness layer around ./constants.ts:
|
|
* - `getStoredMaxaiConstants()` reads the last-extracted, validated constants
|
|
* from OmniRoute settings (the sole source of the two secret-shaped keys).
|
|
* - `persistMaxaiConstants()` writes a freshly-extracted+validated set.
|
|
* - `ensureMaxaiConstants()` is the gate every signed path calls: it returns a
|
|
* usable constants object, extracting + persisting on a cold store, and is
|
|
* cheap (in-process memo) on the hot path.
|
|
* - `refreshMaxaiConstants()` force re-extracts (used by the daily token
|
|
* refresh) so a MaxAI-side rotation is picked up within a day.
|
|
*
|
|
* Design (William's Option 2): there is NO hardcoded fallback for the secret
|
|
* keys. If the store is empty AND a live extraction cannot be validated, the
|
|
* signer has no keys and MaxAI is simply unconfigured (callers surface a clear
|
|
* auth error) — we never sign with a guessed/stale secret.
|
|
*/
|
|
import type { MaxaiSigningConstants, FetchConstantsOptions } from "./constants.ts";
|
|
import {
|
|
MAXAI_CONSTANTS_SETTINGS_KEY,
|
|
fetchMaxaiConstants,
|
|
validateMaxaiConstants,
|
|
MAXAI_DEFAULT_HEADER_NAMES,
|
|
} from "./constants.ts";
|
|
|
|
/** In-process memo so the hot signing path never touches the DB or network. */
|
|
let memo: MaxaiSigningConstants | null = null;
|
|
let inflight: Promise<MaxaiSigningConstants | null> | null = null;
|
|
|
|
/** Reset the in-process memo (tests + after a forced refresh). */
|
|
export function resetMaxaiConstantsMemo(): void {
|
|
memo = null;
|
|
inflight = null;
|
|
}
|
|
|
|
/**
|
|
* Test seam: directly seed the in-process memo so unit tests that exercise the
|
|
* signed network functions don't need to also mock the bundle fetch. Not used in
|
|
* production paths (production goes through ensure/refresh → store → extraction).
|
|
*/
|
|
export function __setMaxaiConstantsForTest(constants: MaxaiSigningConstants | null): void {
|
|
memo = constants;
|
|
inflight = null;
|
|
}
|
|
|
|
/** Shape-guard a persisted record before trusting it. */
|
|
function isUsableConstants(v: unknown): v is MaxaiSigningConstants {
|
|
if (!v || typeof v !== "object") return false;
|
|
const c = v as Partial<MaxaiSigningConstants>;
|
|
return (
|
|
typeof c.hmacKey === "string" &&
|
|
typeof c.aesKey === "string" &&
|
|
typeof c.appVersion === "string" &&
|
|
typeof c.ctxKey === "string" &&
|
|
typeof c.docIdKey === "string" &&
|
|
!!c.headerNames &&
|
|
typeof c.headerNames === "object"
|
|
);
|
|
}
|
|
|
|
/** Read the persisted constants from settings (validated). Null when absent/invalid. */
|
|
export async function getStoredMaxaiConstants(): Promise<MaxaiSigningConstants | null> {
|
|
try {
|
|
const { getSettings } = await import("@/lib/db/settings");
|
|
const settings = await getSettings();
|
|
const raw = (settings as Record<string, unknown>)[MAXAI_CONSTANTS_SETTINGS_KEY];
|
|
if (!isUsableConstants(raw)) return null;
|
|
// Re-validate on read: a persisted record must still reproduce the vector.
|
|
const withDefaults: MaxaiSigningConstants = {
|
|
...raw,
|
|
headerNames: { ...MAXAI_DEFAULT_HEADER_NAMES, ...raw.headerNames },
|
|
};
|
|
return validateMaxaiConstants(withDefaults) ? withDefaults : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/** Persist a freshly-extracted+validated constants set to settings. */
|
|
export async function persistMaxaiConstants(
|
|
constants: MaxaiSigningConstants
|
|
): Promise<void> {
|
|
try {
|
|
const { updateSettings } = await import("@/lib/db/settings");
|
|
await updateSettings({ [MAXAI_CONSTANTS_SETTINGS_KEY]: constants });
|
|
} catch {
|
|
// Non-fatal: a persist failure just means the next process re-extracts.
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return usable MaxAI signing constants, extracting + persisting on a cold store.
|
|
* Order: in-process memo → persisted store → live extraction (validated) → null.
|
|
* Concurrent callers share a single in-flight extraction. Never throws.
|
|
*/
|
|
export async function ensureMaxaiConstants(
|
|
opts: FetchConstantsOptions = {}
|
|
): Promise<MaxaiSigningConstants | null> {
|
|
if (memo) return memo;
|
|
|
|
const stored = await getStoredMaxaiConstants();
|
|
if (stored) {
|
|
memo = stored;
|
|
return memo;
|
|
}
|
|
|
|
if (inflight) return inflight;
|
|
inflight = (async () => {
|
|
try {
|
|
const fresh = await fetchMaxaiConstants(opts);
|
|
if (fresh) {
|
|
memo = fresh;
|
|
await persistMaxaiConstants(fresh);
|
|
return fresh;
|
|
}
|
|
return null;
|
|
} finally {
|
|
inflight = null;
|
|
}
|
|
})();
|
|
return inflight;
|
|
}
|
|
|
|
/**
|
|
* Force a live re-extraction (used by the daily token refresh). If the fetched
|
|
* set validates AND differs from what's stored, it is persisted + memoized so a
|
|
* MaxAI-side rotation is picked up. Returns the current-best constants (the fresh
|
|
* set on success, else whatever was already stored/memoized). Never throws.
|
|
*/
|
|
export async function refreshMaxaiConstants(
|
|
opts: FetchConstantsOptions = {}
|
|
): Promise<MaxaiSigningConstants | null> {
|
|
let fresh: MaxaiSigningConstants | null = null;
|
|
try {
|
|
fresh = await fetchMaxaiConstants(opts);
|
|
} catch {
|
|
fresh = null;
|
|
}
|
|
|
|
if (fresh) {
|
|
const changed =
|
|
!memo ||
|
|
memo.hmacKey !== fresh.hmacKey ||
|
|
memo.aesKey !== fresh.aesKey ||
|
|
memo.appVersion !== fresh.appVersion ||
|
|
memo.ctxKey !== fresh.ctxKey ||
|
|
memo.docIdKey !== fresh.docIdKey;
|
|
memo = fresh;
|
|
if (changed) await persistMaxaiConstants(fresh);
|
|
return fresh;
|
|
}
|
|
|
|
// Fetch failed — keep serving whatever we already have (memo or store).
|
|
return memo ?? (await getStoredMaxaiConstants());
|
|
}
|