Add configurable transient retries for CLI execution
This commit is contained in:
@@ -33,12 +33,13 @@ var (
|
||||
|
||||
// Config is the on-disk YAML shape for application-level settings.
|
||||
type Config struct {
|
||||
PromptDir string `yaml:"prompt_dir"`
|
||||
ProfileDir string `yaml:"profile_dir"`
|
||||
SchemaDir string `yaml:"schema_dir"`
|
||||
Server ServerConfig `yaml:"server"`
|
||||
Defaults DefaultsConfig `yaml:"defaults"`
|
||||
Backends map[string]BackendConfig `yaml:"backends"`
|
||||
PromptDir string `yaml:"prompt_dir"`
|
||||
ProfileDir string `yaml:"profile_dir"`
|
||||
SchemaDir string `yaml:"schema_dir"`
|
||||
TransientRetries *strictInteger `yaml:"transient_retries"`
|
||||
Server ServerConfig `yaml:"server"`
|
||||
Defaults DefaultsConfig `yaml:"defaults"`
|
||||
Backends map[string]BackendConfig `yaml:"backends"`
|
||||
}
|
||||
|
||||
type BackendConfig struct {
|
||||
@@ -49,6 +50,20 @@ type BackendConfig struct {
|
||||
QueueCapacity *int `yaml:"queue_capacity"`
|
||||
}
|
||||
|
||||
type strictInteger int
|
||||
|
||||
func (value *strictInteger) UnmarshalYAML(node *yaml.Node) error {
|
||||
if node.Tag != "!!int" {
|
||||
return fmt.Errorf("expected integer, got %s", node.Tag)
|
||||
}
|
||||
var decoded int
|
||||
if err := node.Decode(&decoded); err != nil {
|
||||
return err
|
||||
}
|
||||
*value = strictInteger(decoded)
|
||||
return nil
|
||||
}
|
||||
|
||||
type BackendSettings struct {
|
||||
ID string
|
||||
Endpoint string
|
||||
@@ -82,6 +97,7 @@ type AppSettings struct {
|
||||
MaxResponseBytes int64
|
||||
DefaultRenderFormat renderformat.PreparedRunOutputFormat
|
||||
Backends []BackendSettings
|
||||
TransientRetries *int
|
||||
}
|
||||
|
||||
// CLIOverrides can be applied after config load to enforce precedence.
|
||||
@@ -224,6 +240,13 @@ func decodeConfig(raw []byte) (Config, error) {
|
||||
|
||||
func applyConfig(base AppSettings, cfg Config) (AppSettings, error) {
|
||||
out := base
|
||||
if cfg.TransientRetries != nil {
|
||||
if *cfg.TransientRetries < 0 {
|
||||
return AppSettings{}, fmt.Errorf("%w: transient_retries must be greater than or equal to 0", ErrInvalidConfig)
|
||||
}
|
||||
value := int(*cfg.TransientRetries)
|
||||
out.TransientRetries = &value
|
||||
}
|
||||
|
||||
if v := strings.TrimSpace(cfg.PromptDir); v != "" {
|
||||
out.PromptDir = filepath.Clean(v)
|
||||
|
||||
@@ -239,6 +239,44 @@ server:
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfigPreservesTransientRetryPresence(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
body string
|
||||
want *int
|
||||
}{
|
||||
{name: "omitted", body: "prompt_dir: ./prompts\n"},
|
||||
{name: "null", body: "transient_retries: null\n"},
|
||||
{name: "zero", body: "transient_retries: 0\n", want: intPointer(0)},
|
||||
{name: "positive", body: "transient_retries: 3\n", want: intPointer(3)},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
settings, err := LoadConfig(writeConfigFile(t, "config.yml", tc.body), true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(settings.TransientRetries, tc.want) {
|
||||
t.Fatalf("retry setting: got %v, want %v", settings.TransientRetries, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
for _, tc := range []struct {
|
||||
body string
|
||||
want error
|
||||
}{
|
||||
{body: "transient_retries: -1\n", want: ErrInvalidConfig},
|
||||
{body: "transient_retries: wrong\n", want: ErrInvalidConfigYAML},
|
||||
{body: "transient_retries: 1.5\n", want: ErrInvalidConfigYAML},
|
||||
} {
|
||||
_, err := LoadConfig(writeConfigFile(t, "config.yml", tc.body), true)
|
||||
if !errors.Is(err, tc.want) {
|
||||
t.Fatalf("body %q: expected %v, got %v", tc.body, tc.want, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func intPointer(value int) *int { return &value }
|
||||
|
||||
func TestLoadConfigRejectsNegativeHTTPSizeLimits(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
Reference in New Issue
Block a user