Files
memind/server/portal-agent-runtime-routes.mjs
T
john fde6503bdf
Memind CI / Test, build, and release guards (push) Has been cancelled
feat(mindspace): add SEO/GEO delivery, page template catalog, and admin hooks
Enable optional SEO/GEO injection and discovery routes for confirmed public pages while keeping private pages noindex. Add premium page template skills, portal catalog API, template shop UI, and Baidu push gated by memind_adm config.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-10 08:06:12 +08:00

207 lines
6.2 KiB
JavaScript

import {
createAgentRunEventsHandler,
createGetAgentRunHandler,
createPostAgentRunsHandler,
} from '../agent-run-routes.mjs';
import { executeDeepSearchLlmGateway } from '../deep-search-llm-gateway.mjs';
function assertRouter(api) {
if (
!api ||
typeof api.get !== 'function' ||
typeof api.post !== 'function'
) {
throw new Error(
'attachPortalAgentRuntimeRoutes requires an Express-compatible router',
);
}
}
function runHandlerChain(chain, req, res, next) {
let index = 0;
const run = (error) => {
if (error) return next(error);
const layer = chain[index++];
if (!layer) return;
layer(req, res, run);
};
run();
}
export function attachPortalAgentRuntimeRoutes(
api,
{
waitForUserAuthReady = async () => {},
getUserAuth = () => null,
getTkmindProxy = () => null,
getLlmProviderService = () => null,
getAgentRunGateway = () => null,
getGoalRunService = () => null,
getChatIntentRouter = () => null,
getTemplateCatalog = () => null,
getSessionAccess = () => null,
getMindSpaceAssetAgent = () => null,
getCodeRunPolicyService = () => null,
getUserToken = () => null,
getDeepSearchInternalSecret = () => null,
bearerToken = () => null,
ownsAgentSession = async () => false,
executeDeepSearchLlmGatewayFn =
executeDeepSearchLlmGateway,
createPostAgentRunsHandlerFn = createPostAgentRunsHandler,
createGetAgentRunHandlerFn = createGetAgentRunHandler,
createAgentRunEventsHandlerFn = createAgentRunEventsHandler,
logger = console,
} = {},
) {
assertRouter(api);
api.post('/internal/deep-search/llm', async (req, res) => {
try {
const result = await executeDeepSearchLlmGatewayFn({
llmProviderService: getLlmProviderService(),
expectedSecret: getDeepSearchInternalSecret(),
providedSecret:
bearerToken(req) ?? req.get('x-secret-key'),
input: req.body ?? {},
});
return res.status(result.status).json(result.body);
} catch (error) {
logger.warn('[deep-search] LLM gateway failed:', error);
return res.status(502).json({
ok: false,
message:
error instanceof Error
? error.message
: 'Deep Search LLM gateway failed',
});
}
});
api.post('/llm/apply-local-fallback', async (req, res) => {
// Client calls after creditsExhausted or relay 500 (see useTKMindChat).
await waitForUserAuthReady();
const userAuth = getUserAuth();
const llmProviderService = getLlmProviderService();
const tkmindProxy = getTkmindProxy();
if (!userAuth || !llmProviderService || !tkmindProxy) {
return res.status(503).json({ message: '未启用 LLM 配置' });
}
const me = await userAuth.getMe(getUserToken(req));
if (!me) return res.status(401).json({ message: '未登录' });
const sessionId = String(req.body?.session_id ?? '').trim();
if (!sessionId) return res.status(400).json({ message: '缺少 session_id' });
const owns = await ownsAgentSession(me.id, sessionId);
if (!owns) return res.status(403).json({ message: '无权访问该会话' });
try {
const result = await tkmindProxy.applyLocalFallbackForSession(sessionId);
if (!result.ok) return res.status(503).json(result);
return res.json(result);
} catch (error) {
return res.status(500).json({
message: error instanceof Error ? error.message : '切换本地 LLM 失败',
});
}
});
api.post('/agent/start', async (req, res, next) => {
await waitForUserAuthReady();
const userAuth = getUserAuth();
const tkmindProxy = getTkmindProxy();
if (!userAuth || !tkmindProxy) return next();
return runHandlerChain(
tkmindProxy.handlers['POST /agent/start'],
req,
res,
next,
);
});
api.post('/agent/runs', async (req, res, next) => {
await waitForUserAuthReady();
const userAuth = getUserAuth();
const tkmindProxy = getTkmindProxy();
const agentRunGateway = getAgentRunGateway();
if (!userAuth || !tkmindProxy || !agentRunGateway) return next();
return runHandlerChain(
[
tkmindProxy.requireUser,
tkmindProxy.ensureChatAllowed,
createPostAgentRunsHandlerFn({
userAuth,
sessionAccess: getSessionAccess(),
agentRunGateway,
mindSpaceAssetAgent: getMindSpaceAssetAgent(),
codeRunPolicyService: getCodeRunPolicyService(),
goalRunService: getGoalRunService(),
chatIntentRouter: getChatIntentRouter(),
templateCatalogService: getTemplateCatalog(),
}),
],
req,
res,
next,
);
});
api.get('/agent/runs/:runId', async (req, res, next) => {
await waitForUserAuthReady();
const userAuth = getUserAuth();
const tkmindProxy = getTkmindProxy();
const agentRunGateway = getAgentRunGateway();
if (!userAuth || !tkmindProxy || !agentRunGateway) return next();
return runHandlerChain(
[
tkmindProxy.requireUser,
createGetAgentRunHandlerFn({ agentRunGateway }),
],
req,
res,
next,
);
});
api.get('/agent/runs/:runId/events', async (req, res, next) => {
await waitForUserAuthReady();
const userAuth = getUserAuth();
const tkmindProxy = getTkmindProxy();
const agentRunGateway = getAgentRunGateway();
if (!userAuth || !tkmindProxy || !agentRunGateway) return next();
return runHandlerChain(
[
tkmindProxy.requireUser,
createAgentRunEventsHandlerFn({ agentRunGateway }),
],
req,
res,
next,
);
});
api.post('/agent/resume', async (req, res, next) => {
await waitForUserAuthReady();
const userAuth = getUserAuth();
const tkmindProxy = getTkmindProxy();
if (!userAuth || !tkmindProxy) return next();
return runHandlerChain(
tkmindProxy.handlers['POST /agent/resume'],
req,
res,
next,
);
});
api.get('/sessions', async (req, res, next) => {
await waitForUserAuthReady();
const userAuth = getUserAuth();
const tkmindProxy = getTkmindProxy();
if (!userAuth || !tkmindProxy) return next();
return runHandlerChain(
tkmindProxy.handlers['GET /sessions'],
req,
res,
next,
);
});
}