82 lines
2.8 KiB
TypeScript
82 lines
2.8 KiB
TypeScript
/**
|
|
* Pool limits — how many bot backends may stay spawned, and how long an
|
|
* unused one survives.
|
|
*
|
|
* A device-local preference (each machine trades RAM against switching
|
|
* speed for itself), stored in userData like keep-awake. The main process
|
|
* is authoritative: it owns the pool AND the persisted copy, and applies a
|
|
* new max IMMEDIATELY by evicting least-recently-used idle backends — no
|
|
* app restart. The renderer mirrors the values for its UI and prewarm
|
|
* guard over IPC.
|
|
*
|
|
* Defaults preserve the historical hard-coded behavior (3 backends, 10min
|
|
* idle) so machines that never open Settings behave exactly as before.
|
|
*/
|
|
|
|
export interface PoolLimits {
|
|
/** Max concurrently spawned non-primary profile backends. */
|
|
maxBackends: number
|
|
/** Idle lifetime of an unused pool backend, in milliseconds. */
|
|
idleMs: number
|
|
}
|
|
|
|
export const POOL_LIMITS_DEFAULTS: PoolLimits = {
|
|
maxBackends: 3,
|
|
idleMs: 10 * 60_000
|
|
}
|
|
|
|
/** Hard floors — match the clamps the env-var path always applied. */
|
|
export const POOL_LIMITS_MIN: PoolLimits = {
|
|
maxBackends: 1,
|
|
idleMs: 60_000
|
|
}
|
|
|
|
/** Shared bounds for both pool knobs — imported by the Settings UI so the
|
|
* advertised input ranges can never drift from what main actually clamps
|
|
* to. idleMs has no ceiling: a user who wants backends kept warm all week
|
|
* may have exactly that. */
|
|
export const POOL_LIMITS_BOUNDS = {
|
|
maxBackendsMax: 64,
|
|
/** 7 days, matching the UI's suggestion ceiling. */
|
|
idleMsMax: 7 * 24 * 60 * 60_000
|
|
} as const
|
|
|
|
const MAX_BACKENDS_CEILING = POOL_LIMITS_BOUNDS.maxBackendsMax
|
|
const IDLE_MS_CEILING = POOL_LIMITS_BOUNDS.idleMsMax
|
|
|
|
/** Clamp a raw partial to the floors/ceilings; missing keys fall to defaults. */
|
|
export function clampPoolLimits(raw: Partial<PoolLimits>): PoolLimits {
|
|
const maxBackends = Number.isFinite(raw.maxBackends)
|
|
? Math.min(MAX_BACKENDS_CEILING, Math.max(POOL_LIMITS_MIN.maxBackends, Math.floor(Number(raw.maxBackends))))
|
|
: POOL_LIMITS_DEFAULTS.maxBackends
|
|
|
|
const idleMs = Number.isFinite(raw.idleMs)
|
|
? Math.min(IDLE_MS_CEILING, Math.max(POOL_LIMITS_MIN.idleMs, Math.floor(Number(raw.idleMs))))
|
|
: POOL_LIMITS_DEFAULTS.idleMs
|
|
|
|
return { maxBackends, idleMs }
|
|
}
|
|
|
|
function clampLimits(raw: Partial<PoolLimits>): PoolLimits {
|
|
return clampPoolLimits(raw)
|
|
}
|
|
|
|
/** Parse + clamp a persisted JSON blob; anything unreadable falls back to
|
|
* defaults so a corrupted file can never wedge the pool. */
|
|
export function parsePoolLimits(json: string | null | undefined): PoolLimits {
|
|
if (!json) {
|
|
return { ...POOL_LIMITS_DEFAULTS }
|
|
}
|
|
|
|
try {
|
|
const parsed = JSON.parse(json)
|
|
|
|
return clampLimits({
|
|
maxBackends: typeof parsed?.maxBackends === 'number' ? parsed.maxBackends : undefined,
|
|
idleMs: typeof parsed?.idleMs === 'number' ? parsed.idleMs : undefined
|
|
})
|
|
} catch {
|
|
return { ...POOL_LIMITS_DEFAULTS }
|
|
}
|
|
}
|