feat: conversion-driven extensions — resolvers, DefineTool, hooks, ops controls
Phase 9a (ADR-0014): Registry.RegisterResolver for dynamic tiers; DefineTool[Args] typed tools; Usage cache/reasoning detail fields wired through anthropic/openai/google; WithPromptCaching (Anthropic cache_control); agent supervision hooks (WithMaxStepsFunc, WithSteer, WithCompactor, WithToolErrorLimits + ErrToolLoop); health Bench/Unbench/Snapshot; ChainConfig.Observer failover events. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,8 @@
|
||||
package health
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -133,6 +135,57 @@ func (t *Tracker) ReportFailure(key string) (backedOff bool) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Bench manually benches a key until the given time, regardless of its
|
||||
// failure history (ops surfaces: ".failover bench"). A zero time unbenches.
|
||||
func (t *Tracker) Bench(key string, until time.Time) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
e, ok := t.entries[key]
|
||||
if !ok {
|
||||
e = &entry{}
|
||||
t.entries[key] = e
|
||||
}
|
||||
e.until = until
|
||||
}
|
||||
|
||||
// Unbench clears a key entirely: bench window, failure count, and backoff
|
||||
// history.
|
||||
func (t *Tracker) Unbench(key string) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
delete(t.entries, key)
|
||||
}
|
||||
|
||||
// Status describes one tracked key (diagnostics).
|
||||
type Status struct {
|
||||
Key string
|
||||
// Until is the end of the current bench window (zero = not benched).
|
||||
Until time.Time
|
||||
// ConsecutiveFailures since the last success or bench trigger.
|
||||
ConsecutiveFailures int
|
||||
// Benches is the consecutive backoff count driving the exponent.
|
||||
Benches int
|
||||
}
|
||||
|
||||
// Snapshot returns the status of every currently-benched key, sorted by
|
||||
// key, evaluated at the tracker's clock.
|
||||
func (t *Tracker) Snapshot() []Status {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
now := t.cfg.Clock()
|
||||
out := make([]Status, 0, len(t.entries))
|
||||
for key, e := range t.entries {
|
||||
if now.Before(e.until) {
|
||||
out = append(out, Status{
|
||||
Key: key, Until: e.until,
|
||||
ConsecutiveFailures: e.consecutiveFailures, Benches: e.backoffs,
|
||||
})
|
||||
}
|
||||
}
|
||||
slices.SortFunc(out, func(a, b Status) int { return strings.Compare(a.Key, b.Key) })
|
||||
return out
|
||||
}
|
||||
|
||||
// BackedOffUntil returns the end of the key's current backoff window, or the
|
||||
// zero time when the key is not backed off. Useful for diagnostics and error
|
||||
// messages.
|
||||
|
||||
Reference in New Issue
Block a user