blob: e129fb041eacafcd6f87fb7a9b326d395283193a (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
-- Timing policy for session warming: WHEN a transient request may fire.
--
-- The semantics mirror opencode's warming feature. `interval` is the idle
-- time before a warm request fires after real activity (and again after each
-- warm attempt); `duration` is the warming window measured from the latest
-- non-warming activity. Warm attempts never extend the window:
--
-- 00:00 real activity window = [00:00, 00:30]
-- 00:04 warm next due 00:08
-- 00:08 warm ...
-- 00:30 window expired, warming stops
--
-- A turn in flight (`turn_started` without `turn_ended`) blocks warm
-- requests: the live conversation is changing under us. With
-- `interval >= duration` the window always expires before the first warm
-- request is due, so warming never fires -- a documented consequence of the
-- opencode semantics, not a special case.
--
-- Every entry point takes `now` (monotonic milliseconds) so the policy is
-- pure and selfcheck-able; scheduling and IO live elsewhere.
local M = {}
local Policy = {}
Policy.__index = Policy
-- interval_ms and duration_ms are validated positive by config parsing.
function M.new(interval_ms, duration_ms)
return setmetatable({
interval = interval_ms,
duration = duration_ms,
last_real = nil, -- ms of the latest non-warming activity
next_warm = nil, -- ms before which no warm request may fire
deadline = nil, -- ms at which the warming window closes
busy = false, -- a real turn is in flight
}, Policy)
end
function Policy:activity(now)
self.last_real = now
self.next_warm = now + self.interval
self.deadline = now + self.duration
end
function Policy:turn_started(now)
self.busy = true
self:activity(now)
end
function Policy:turn_ended(now)
self.busy = false
self:activity(now)
end
-- May a warm request start right now?
function Policy:due(now)
return (not self.busy)
and self.next_warm ~= nil
and now >= self.next_warm
and now < self.deadline
end
-- A warm attempt settled (success or failure): schedule the next one. A
-- failure waits out the same interval and the original window keeps running.
function Policy:attempted(now)
self.next_warm = now + self.interval
end
-- Nothing left to do until real activity reopens the window. Lets the
-- caller stop its timer instead of ticking forever.
function Policy:expired(now)
return self.deadline == nil or now >= self.deadline
end
return M
|