import { buildContextPlan } from '../temporal-recall-service/context-planner.mjs'; import { queryTemporalRecall } from '../temporal-recall-service/recall.mjs'; import { resolveCanonicalUserId } from '../user-model-service/canonical-user.mjs'; function assertRouter(api) { if (!api || typeof api.get !== 'function' || typeof api.post !== 'function') { throw new Error('attachPortalTemporalRecallRoutes requires an Express-compatible router'); } } function parseBearerToken(req) { const auth = req.headers.authorization ?? ''; return auth.startsWith('Bearer ') ? auth.slice(7).trim() : ''; } function createResolveUser(getUserAuth) { return async function resolveTemporalUser(req, _res, next) { if (req.currentUser?.id) return next(); const bearer = parseBearerToken(req); const userAuth = getUserAuth?.(); if (!bearer || !userAuth?.getMe) return next(); try { const me = await userAuth.getMe(bearer); if (me) req.currentUser = me; } catch { /* ignore */ } return next(); }; } function createRequireUser() { return function requireUser(req, res, next) { if (!req.currentUser?.id) { return res.status(401).json({ message: '未授权,请重新登录' }); } return next(); }; } export function attachPortalTemporalRecallRoutes(api, { getPool, getUserAuth }) { assertRouter(api); const resolveTemporalUser = createResolveUser(getUserAuth); const requireUser = createRequireUser(); api.use('/v1/context', resolveTemporalUser); api.use('/v1/temporal-recall', resolveTemporalUser); api.post('/v1/context/plan', requireUser, (req, res) => { try { const query = String(req.body?.query ?? '').trim(); if (!query) return res.status(400).json({ error: 'invalid_query' }); const plannerLevel = String(req.body?.planner_level ?? 'auto'); if (plannerLevel === 'semantic') { return res.status(501).json({ error: 'not_implemented', message: 'semantic planner v0.2' }); } const plan = buildContextPlan({ query, user_id: resolveCanonicalUserId(req.currentUser.id), now: req.body?.now ? new Date(req.body.now) : new Date(), planner_level: plannerLevel, }); return res.json({ plan }); } catch (err) { if (err instanceof Error && err.message === 'invalid_query') { return res.status(400).json({ error: 'invalid_query' }); } console.error('[temporal] plan failed', err); return res.status(500).json({ error: err instanceof Error ? err.message : 'plan failed' }); } }); api.get('/v1/temporal-recall/info', (_req, res) => { return res.json({ schema_version: 1, supported_sources: ['calendar', 'meinput', 'chat'], planned_sources: ['memory_v2', 'email', 'browser', 'tasks'], temporal_modes: [ 'OCCURRED_IN', 'MENTIONED_IN', 'PLANNED_IN', 'CREATED_IN', 'DUE_IN', 'AMBIGUOUS', ], default_limit: 50, max_limit: 200, }); }); api.post('/v1/temporal-recall/query', requireUser, async (req, res) => { try { const pool = getPool?.(); const userId = resolveCanonicalUserId(req.currentUser.id); const body = req.body ?? {}; const result = await queryTemporalRecall(pool, { query: body.query, plan: body.plan, user_id: userId, now: body.now ? new Date(body.now) : new Date(), session_id: body.session_id, limit: body.limit, }); return res.json(result); } catch (err) { console.error('[temporal] query failed', err); return res.status(500).json({ error: err instanceof Error ? err.message : 'query failed' }); } }); }