feat(server): hot-reload config store and reloadable infra clients
- Replace single Load() with a watching Store that re-reads config files
(and config.local.yaml) and fans out a ReloadEvent with a per-field diff
so consumers can decide whether the change is hot-applicable or requires
a process restart.
- Wrap llm, retrieval, vector store, and object storage clients in
Reloadable* shells so the bootstrap can swap their underlying impls when
config changes without re-instantiating handlers.
- Make jwt.Manager and ops TokenIssuer mutable under a lock so secrets and
TTLs can be rotated live; thread default plan code through a setter on
the ops AdminUserService.
- Wire ConfigStore through bootstrap and every cmd/main.go, scheduler /
worker / tenant-api / ops-api start the watcher; services and handlers
take a config.Provider so they always read current values for things
like generation.stream_enabled, scheduler dispatch, retrieval, etc.
- Switch shared/config decoding off viper to a Kratos-derived runtime
package so env placeholders (\${VAR:default}) resolve consistently and
the same source machinery powers both the loader and the watcher.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Value interface {
|
||||
Bool() (bool, error)
|
||||
Int() (int64, error)
|
||||
Float() (float64, error)
|
||||
String() (string, error)
|
||||
Duration() (time.Duration, error)
|
||||
Slice() ([]Value, error)
|
||||
Map() (map[string]Value, error)
|
||||
Scan(any) error
|
||||
Load() any
|
||||
Store(any)
|
||||
}
|
||||
|
||||
type atomicValue struct {
|
||||
atomic.Value
|
||||
}
|
||||
|
||||
func (v *atomicValue) typeAssertError() error {
|
||||
return fmt.Errorf("type assert to %v failed", reflect.TypeOf(v.Load()))
|
||||
}
|
||||
|
||||
func (v *atomicValue) Bool() (bool, error) {
|
||||
switch value := v.Load().(type) {
|
||||
case bool:
|
||||
return value, nil
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
|
||||
return strconv.ParseBool(fmt.Sprint(value))
|
||||
case string:
|
||||
return strconv.ParseBool(value)
|
||||
}
|
||||
return false, v.typeAssertError()
|
||||
}
|
||||
|
||||
func (v *atomicValue) Int() (int64, error) {
|
||||
switch value := v.Load().(type) {
|
||||
case int:
|
||||
return int64(value), nil
|
||||
case int8:
|
||||
return int64(value), nil
|
||||
case int16:
|
||||
return int64(value), nil
|
||||
case int32:
|
||||
return int64(value), nil
|
||||
case int64:
|
||||
return value, nil
|
||||
case uint:
|
||||
return int64(value), nil
|
||||
case uint8:
|
||||
return int64(value), nil
|
||||
case uint16:
|
||||
return int64(value), nil
|
||||
case uint32:
|
||||
return int64(value), nil
|
||||
case uint64:
|
||||
return int64(value), nil
|
||||
case float32:
|
||||
return int64(value), nil
|
||||
case float64:
|
||||
return int64(value), nil
|
||||
case string:
|
||||
return strconv.ParseInt(value, 10, 64)
|
||||
}
|
||||
return 0, v.typeAssertError()
|
||||
}
|
||||
|
||||
func (v *atomicValue) Float() (float64, error) {
|
||||
switch value := v.Load().(type) {
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
|
||||
return strconv.ParseFloat(fmt.Sprint(value), 64)
|
||||
case string:
|
||||
return strconv.ParseFloat(value, 64)
|
||||
}
|
||||
return 0, v.typeAssertError()
|
||||
}
|
||||
|
||||
func (v *atomicValue) String() (string, error) {
|
||||
switch value := v.Load().(type) {
|
||||
case string:
|
||||
return value, nil
|
||||
case bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
|
||||
return fmt.Sprint(value), nil
|
||||
case []byte:
|
||||
return string(value), nil
|
||||
case fmt.Stringer:
|
||||
return value.String(), nil
|
||||
}
|
||||
return "", v.typeAssertError()
|
||||
}
|
||||
|
||||
func (v *atomicValue) Duration() (time.Duration, error) {
|
||||
value, err := v.Int()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return time.Duration(value), nil
|
||||
}
|
||||
|
||||
func (v *atomicValue) Slice() ([]Value, error) {
|
||||
values, ok := v.Load().([]any)
|
||||
if !ok {
|
||||
return nil, v.typeAssertError()
|
||||
}
|
||||
out := make([]Value, 0, len(values))
|
||||
for _, value := range values {
|
||||
item := &atomicValue{}
|
||||
item.Store(value)
|
||||
out = append(out, item)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (v *atomicValue) Map() (map[string]Value, error) {
|
||||
values, ok := v.Load().(map[string]any)
|
||||
if !ok {
|
||||
return nil, v.typeAssertError()
|
||||
}
|
||||
out := make(map[string]Value, len(values))
|
||||
for key, value := range values {
|
||||
item := &atomicValue{}
|
||||
item.Store(value)
|
||||
out[key] = item
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (v *atomicValue) Scan(obj any) error {
|
||||
data, err := json.Marshal(v.Load())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(data, obj)
|
||||
}
|
||||
|
||||
type errValue struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (v errValue) Bool() (bool, error) { return false, v.err }
|
||||
func (v errValue) Int() (int64, error) { return 0, v.err }
|
||||
func (v errValue) Float() (float64, error) { return 0, v.err }
|
||||
func (v errValue) Duration() (time.Duration, error) { return 0, v.err }
|
||||
func (v errValue) String() (string, error) { return "", v.err }
|
||||
func (v errValue) Scan(any) error { return v.err }
|
||||
func (v errValue) Load() any { return nil }
|
||||
func (v errValue) Store(any) {}
|
||||
func (v errValue) Slice() ([]Value, error) { return nil, v.err }
|
||||
func (v errValue) Map() (map[string]Value, error) { return nil, v.err }
|
||||
Reference in New Issue
Block a user