mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-25 07:00:48 +09:00
92 lines
2.2 KiB
Go
92 lines
2.2 KiB
Go
package flow
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// idleCancel wraps a resettable idle timer. If Reset() is not called before
|
|
// the timer fires, the underlying context is cancelled.
|
|
type idleCancel struct {
|
|
cancel context.CancelFunc
|
|
timer *time.Timer
|
|
mu sync.Mutex
|
|
fired bool
|
|
baseTimeout time.Duration
|
|
toolCalls int
|
|
}
|
|
|
|
func (ic *idleCancel) Reset() {
|
|
ic.mu.Lock()
|
|
defer ic.mu.Unlock()
|
|
if !ic.fired {
|
|
ic.timer.Stop()
|
|
ic.timer.Reset(ic.currentTimeout())
|
|
}
|
|
}
|
|
|
|
// RecordToolCall increments the tool call counter and extends the idle timeout.
|
|
func (ic *idleCancel) RecordToolCall() {
|
|
ic.mu.Lock()
|
|
defer ic.mu.Unlock()
|
|
ic.toolCalls++
|
|
}
|
|
|
|
func (ic *idleCancel) Stop() {
|
|
ic.mu.Lock()
|
|
defer ic.mu.Unlock()
|
|
ic.timer.Stop()
|
|
}
|
|
|
|
func (ic *idleCancel) DidFire() bool {
|
|
ic.mu.Lock()
|
|
defer ic.mu.Unlock()
|
|
return ic.fired
|
|
}
|
|
|
|
// ToolCalls returns the number of tool calls recorded.
|
|
func (ic *idleCancel) ToolCalls() int {
|
|
ic.mu.Lock()
|
|
defer ic.mu.Unlock()
|
|
return ic.toolCalls
|
|
}
|
|
|
|
// currentTimeout returns the adaptive timeout: base + 60s per tool call, capped at 600s.
|
|
// Tool calls (especially spawn/subagent) can take minutes to complete, so the
|
|
// extension per tool call is generous to avoid interrupting active work.
|
|
func (ic *idleCancel) currentTimeout() time.Duration {
|
|
extra := time.Duration(ic.toolCalls) * 60 * time.Second
|
|
timeout := ic.baseTimeout + extra
|
|
if timeout > 600*time.Second {
|
|
timeout = 600 * time.Second
|
|
}
|
|
return timeout
|
|
}
|
|
|
|
const defaultIdleTimeout = 90 * time.Second
|
|
|
|
// withIdleTimeout returns a context that is cancelled if no Reset() call is
|
|
// made within the adaptive idle timeout. The returned idleCancel must have
|
|
// Reset() called for each meaningful event to prevent the timeout from firing.
|
|
// The timeout adapts: base + 10s per tool call, capped at 300s.
|
|
func withIdleTimeout(parent context.Context, baseTimeout ...time.Duration) (context.Context, *idleCancel) {
|
|
bt := defaultIdleTimeout
|
|
if len(baseTimeout) > 0 && baseTimeout[0] > 0 {
|
|
bt = baseTimeout[0]
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(parent)
|
|
ic := &idleCancel{
|
|
cancel: cancel,
|
|
baseTimeout: bt,
|
|
}
|
|
ic.timer = time.AfterFunc(bt, func() {
|
|
ic.mu.Lock()
|
|
ic.fired = true
|
|
ic.mu.Unlock()
|
|
cancel()
|
|
})
|
|
return ctx, ic
|
|
}
|