hosted-consolidation-advanced.mjs
133 lines 5.2 KB
Raw
sha256:8d46372e39d2d5a54fd93a8b1c27922fe0d9b22a72197345f1d2c71701cc4ce2 feat(auth): persistent login system + C7 session introspection Human minor ⚠ breaking 16 days ago
1 /**
2 * Hosted consolidation "advanced" knobs — same ranges as self-hosted Hub POST
3 * (hub/server.mjs /api/v1/settings/consolidation). Used by gateway, bridge, and scheduler.
4 */
5
6 export const HOSTED_CONSOL_LOOKBACK_MIN = 1;
7 export const HOSTED_CONSOL_LOOKBACK_MAX = 8760;
8 export const HOSTED_CONSOL_MAX_EVENTS_MIN = 1;
9 export const HOSTED_CONSOL_MAX_EVENTS_MAX = 10000;
10 export const HOSTED_CONSOL_MAX_TOPICS_MIN = 1;
11 export const HOSTED_CONSOL_MAX_TOPICS_MAX = 500;
12 export const HOSTED_CONSOL_LLM_TOKENS_MIN = 64;
13 export const HOSTED_CONSOL_LLM_TOKENS_MAX = 8192;
14
15 export const HOSTED_CONSOL_DEFAULT_LOOKBACK_HOURS = 24;
16 export const HOSTED_CONSOL_DEFAULT_MAX_EVENTS = 200;
17 export const HOSTED_CONSOL_DEFAULT_MAX_TOPICS = 10;
18 export const HOSTED_CONSOL_DEFAULT_LLM_MAX_TOKENS = 1024;
19
20 /**
21 * Clamp integer n to [lo, hi]; invalid → fallback.
22 * @param {unknown} n
23 * @param {number} lo
24 * @param {number} hi
25 * @param {number} fallback
26 */
27 export function clampConsolidationInt(n, lo, hi, fallback) {
28 const x = Math.floor(Number(n));
29 if (!Number.isFinite(x)) return fallback;
30 return Math.min(hi, Math.max(lo, x));
31 }
32
33 /**
34 * Read advanced defaults from a normalized billing user record.
35 * @param {object|null|undefined} u
36 * @returns {{ lookback_hours: number, max_events_per_pass: number, max_topics_per_pass: number, llm_max_tokens: number }}
37 */
38 export function hostedAdvancedFromBillingUser(u) {
39 if (!u || typeof u !== 'object') {
40 return {
41 lookback_hours: HOSTED_CONSOL_DEFAULT_LOOKBACK_HOURS,
42 max_events_per_pass: HOSTED_CONSOL_DEFAULT_MAX_EVENTS,
43 max_topics_per_pass: HOSTED_CONSOL_DEFAULT_MAX_TOPICS,
44 llm_max_tokens: HOSTED_CONSOL_DEFAULT_LLM_MAX_TOKENS,
45 };
46 }
47 return {
48 lookback_hours: clampConsolidationInt(
49 u.consolidation_lookback_hours,
50 HOSTED_CONSOL_LOOKBACK_MIN,
51 HOSTED_CONSOL_LOOKBACK_MAX,
52 HOSTED_CONSOL_DEFAULT_LOOKBACK_HOURS,
53 ),
54 max_events_per_pass: clampConsolidationInt(
55 u.consolidation_max_events_per_pass,
56 HOSTED_CONSOL_MAX_EVENTS_MIN,
57 HOSTED_CONSOL_MAX_EVENTS_MAX,
58 HOSTED_CONSOL_DEFAULT_MAX_EVENTS,
59 ),
60 max_topics_per_pass: clampConsolidationInt(
61 u.consolidation_max_topics_per_pass,
62 HOSTED_CONSOL_MAX_TOPICS_MIN,
63 HOSTED_CONSOL_MAX_TOPICS_MAX,
64 HOSTED_CONSOL_DEFAULT_MAX_TOPICS,
65 ),
66 llm_max_tokens: clampConsolidationInt(
67 u.consolidation_llm_max_tokens,
68 HOSTED_CONSOL_LLM_TOKENS_MIN,
69 HOSTED_CONSOL_LLM_TOKENS_MAX,
70 HOSTED_CONSOL_DEFAULT_LLM_MAX_TOKENS,
71 ),
72 };
73 }
74
75 /**
76 * Merge JSON body for POST /memory/consolidate: explicit body fields win; missing keys use billing user.
77 * @param {object} body - request body (may be partial)
78 * @param {object} billingUser - normalizeBillingUser() output
79 * @returns {object} shallow copy with lookback_hours, max_events_per_pass, max_topics_per_pass, llm.max_tokens filled
80 */
81 export function mergeConsolidateRequestBodyWithBillingDefaults(body, billingUser) {
82 const src = body && typeof body === 'object' ? body : {};
83 const out = { ...src };
84 const d = hostedAdvancedFromBillingUser(billingUser);
85
86 if (out.lookback_hours == null) out.lookback_hours = d.lookback_hours;
87 if (out.max_events_per_pass == null) out.max_events_per_pass = d.max_events_per_pass;
88 if (out.max_topics_per_pass == null) out.max_topics_per_pass = d.max_topics_per_pass;
89
90 const prevLlm = out.llm && typeof out.llm === 'object' ? out.llm : {};
91 if (prevLlm.max_tokens == null) {
92 out.llm = { ...prevLlm, max_tokens: d.llm_max_tokens };
93 } else {
94 out.llm = { ...prevLlm };
95 }
96
97 return out;
98 }
99
100 /**
101 * Validate optional advanced fields on POST /api/v1/settings/consolidation (hosted gateway).
102 * Only checks keys that are present on body.
103 * @param {object} body
104 * @returns {{ ok: true } | { ok: false, error: string, code: string }}
105 */
106 export function validateHostedSettingsConsolidationAdvanced(body) {
107 if (!body || typeof body !== 'object') return { ok: true };
108 if (body.lookback_hours !== undefined) {
109 const lb = Math.floor(Number(body.lookback_hours));
110 if (!Number.isFinite(lb) || lb < HOSTED_CONSOL_LOOKBACK_MIN || lb > HOSTED_CONSOL_LOOKBACK_MAX) {
111 return { ok: false, error: 'lookback_hours must be 1–8760', code: 'VALIDATION_ERROR' };
112 }
113 }
114 if (body.max_events_per_pass !== undefined) {
115 const me = Math.floor(Number(body.max_events_per_pass));
116 if (!Number.isFinite(me) || me < HOSTED_CONSOL_MAX_EVENTS_MIN || me > HOSTED_CONSOL_MAX_EVENTS_MAX) {
117 return { ok: false, error: 'max_events_per_pass must be 1–10000', code: 'VALIDATION_ERROR' };
118 }
119 }
120 if (body.max_topics_per_pass !== undefined) {
121 const mt = Math.floor(Number(body.max_topics_per_pass));
122 if (!Number.isFinite(mt) || mt < HOSTED_CONSOL_MAX_TOPICS_MIN || mt > HOSTED_CONSOL_MAX_TOPICS_MAX) {
123 return { ok: false, error: 'max_topics_per_pass must be 1–500', code: 'VALIDATION_ERROR' };
124 }
125 }
126 if (body.llm !== undefined && typeof body.llm === 'object' && body.llm.max_tokens !== undefined) {
127 const mxt = Math.floor(Number(body.llm.max_tokens));
128 if (!Number.isFinite(mxt) || mxt < HOSTED_CONSOL_LLM_TOKENS_MIN || mxt > HOSTED_CONSOL_LLM_TOKENS_MAX) {
129 return { ok: false, error: 'llm.max_tokens must be 64–8192', code: 'VALIDATION_ERROR' };
130 }
131 }
132 return { ok: true };
133 }
File History 2 commits
sha256:8d46372e39d2d5a54fd93a8b1c27922fe0d9b22a72197345f1d2c71701cc4ce2 feat(auth): persistent login system + C7 session introspection Human minor 16 days ago