Files
memind/memind-headroom-policy.mjs
T
john 26ce1241c6 fix(context): repair headroom probe routing and add portal E2E gate
Use the default headroom health probe instead of the goosed fetch adapter
so active mode routes deepseek-no-think through the proxy, and add an E2E
script that verifies goosed pong via headroom with fail-open portal checks.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 22:01:29 +08:00

190 lines
5.7 KiB
JavaScript

import { spawn } from 'node:child_process';
import {
resolveDeepseekNoThinkProxyBaseUrl,
} from './deepseek-no-think-proxy.mjs';
export const DEFAULT_HEADROOM_PROXY_PORT = 8787;
export const MEMIND_HEADROOM_MODES = Object.freeze(['off', 'shadow', 'active']);
export const DEFAULT_HEADROOM_EXCLUDED_SKILLS = Object.freeze([
'static-page-publish',
'page-data-collect',
'aider-development',
]);
function normalizeMode(value, fallback = 'off') {
const raw = String(value ?? fallback).trim().toLowerCase();
return MEMIND_HEADROOM_MODES.includes(raw) ? raw : fallback;
}
export function resolveHeadroomMode(env = process.env) {
return normalizeMode(env.MEMIND_HEADROOM_MODE, 'off');
}
export function resolveHeadroomProxyPort(env = process.env) {
const port = Number(env.MEMIND_HEADROOM_PROXY_PORT ?? DEFAULT_HEADROOM_PROXY_PORT);
return Number.isFinite(port) && port > 0 ? port : DEFAULT_HEADROOM_PROXY_PORT;
}
export function resolveHeadroomProxyBaseUrl(env = process.env) {
const explicit = String(env.MEMIND_HEADROOM_PROXY_BASE_URL ?? '').trim();
if (explicit) return explicit.replace(/\/$/, '');
const host = String(env.MEMIND_HEADROOM_PROXY_HOST ?? '127.0.0.1').trim() || '127.0.0.1';
return `http://${host}:${resolveHeadroomProxyPort(env)}/v1`;
}
export function resolveHeadroomUpstreamBaseUrl(env = process.env) {
const explicit = String(env.MEMIND_HEADROOM_UPSTREAM_BASE_URL ?? '').trim();
if (explicit) return explicit.replace(/\/$/, '');
return resolveDeepseekNoThinkProxyBaseUrl(env);
}
export function parseHeadroomExcludedSkills(env = process.env) {
const raw = String(env.MEMIND_HEADROOM_EXCLUDED_SKILLS ?? '').trim();
if (!raw) return [...DEFAULT_HEADROOM_EXCLUDED_SKILLS];
return raw.split(',').map((item) => item.trim()).filter(Boolean);
}
export function isHeadroomEligibleForSkill(skillId, env = process.env) {
if (resolveHeadroomMode(env) === 'off') return false;
const skill = String(skillId ?? '').trim();
if (!skill) return true;
return !parseHeadroomExcludedSkills(env).includes(skill);
}
/**
* Decide whether goosed should talk to headroom instead of the direct compat proxy.
* fail-open: active mode falls back to the original apiUrl when headroom is unreachable.
*/
export function resolveGoosedApiUrlWithHeadroom({
apiUrl,
mode = resolveHeadroomMode(),
headroomReachable = false,
eligible = true,
env = process.env,
} = {}) {
const normalizedMode = normalizeMode(mode, 'off');
const original = String(apiUrl ?? '').trim();
if (!original || normalizedMode === 'off' || !eligible) {
return {
apiUrl: original,
mode: normalizedMode,
routed: false,
eligible,
};
}
const headroomBase = resolveHeadroomProxyBaseUrl(env);
if (normalizedMode === 'shadow') {
return {
apiUrl: original,
mode: 'shadow',
routed: false,
eligible: true,
wouldRouteTo: headroomBase,
upstreamBaseUrl: resolveHeadroomUpstreamBaseUrl(env),
};
}
if (!headroomReachable) {
return {
apiUrl: original,
mode: 'active',
routed: false,
eligible: true,
fallback: 'headroom_unreachable',
wouldRouteTo: headroomBase,
};
}
return {
apiUrl: headroomBase,
mode: 'active',
routed: true,
eligible: true,
upstreamBaseUrl: resolveHeadroomUpstreamBaseUrl(env),
};
}
export function buildHeadroomRunObservation({ skillId, env = process.env } = {}) {
const mode = resolveHeadroomMode(env);
const eligible = isHeadroomEligibleForSkill(skillId, env);
return {
mode,
eligible,
excludedSkills: parseHeadroomExcludedSkills(env),
proxyBaseUrl: mode !== 'off' ? resolveHeadroomProxyBaseUrl(env) : null,
upstreamBaseUrl: mode !== 'off' ? resolveHeadroomUpstreamBaseUrl(env) : null,
};
}
export async function ensureHeadroomProxyRunning({
env = process.env,
spawnImpl = spawn,
startupWaitMs = 20_000,
} = {}) {
const baseUrl = resolveHeadroomProxyBaseUrl(env);
if (await probeHeadroomProxyReachable({ baseUrl, env })) {
return { started: false, baseUrl, reachable: true };
}
const upstream = resolveHeadroomUpstreamBaseUrl(env);
const port = resolveHeadroomProxyPort(env);
const host = String(env.MEMIND_HEADROOM_PROXY_HOST ?? '127.0.0.1').trim() || '127.0.0.1';
const openaiApiRoot = upstream.replace(/\/v1\/?$/, '');
const child = spawnImpl(
'headroom',
['proxy', '--port', String(port), '--host', host, '--openai-api-url', openaiApiRoot],
{
detached: true,
stdio: 'ignore',
env: {
...process.env,
...env,
OPENAI_TARGET_API_URL: upstream,
HEADROOM_OUTPUT_SHAPER: '0',
},
},
);
child.unref?.();
const deadline = Date.now() + startupWaitMs;
while (Date.now() < deadline) {
await new Promise((resolve) => setTimeout(resolve, 1000));
if (await probeHeadroomProxyReachable({ baseUrl, env })) {
return { started: true, baseUrl, reachable: true, upstream, port };
}
}
return { started: true, baseUrl, reachable: false, upstream, port };
}
export async function probeHeadroomProxyReachable({
baseUrl,
fetchImpl = fetch,
timeoutMs = 1500,
env = process.env,
} = {}) {
const root = String(baseUrl ?? resolveHeadroomProxyBaseUrl(env))
.trim()
.replace(/\/v1\/?$/i, '');
if (!root) return false;
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetchImpl(`${root}/v1/models`, {
method: 'GET',
signal: controller.signal,
headers: { Authorization: 'Bearer headroom-probe' },
});
// Proxy alive when it responds, even with auth/upstream errors.
return response.status > 0;
} catch {
return false;
} finally {
clearTimeout(timer);
}
}