Files
root 618399f86d 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>
2026-05-01 16:01:23 +08:00

159 lines
3.8 KiB
Go

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 }