116 lines
2.8 KiB
Go
116 lines
2.8 KiB
Go
package taskutil
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
|
|
"git.gobha.me/xcaliber/chat-switchboard/models"
|
|
"git.gobha.me/xcaliber/chat-switchboard/store"
|
|
)
|
|
|
|
// TaskConfig holds the runtime task configuration read from global_settings.
|
|
// Keys: tasks.enabled, tasks.allow_personal, tasks.max_concurrent,
|
|
// tasks.default_max_tokens, tasks.default_max_tool_calls,
|
|
// tasks.default_max_wall_clock, tasks.personal_require_byok
|
|
type TaskConfig struct {
|
|
Enabled bool
|
|
AllowPersonal bool
|
|
PersonalRequireBYOK bool
|
|
MaxConcurrent int
|
|
DefaultMaxTokens int
|
|
DefaultMaxToolCalls int
|
|
DefaultMaxWallClock int // seconds
|
|
}
|
|
|
|
// DefaultTaskConfig returns sensible defaults when no global config is set.
|
|
func DefaultTaskConfig() TaskConfig {
|
|
return TaskConfig{
|
|
Enabled: true,
|
|
AllowPersonal: true,
|
|
PersonalRequireBYOK: false,
|
|
MaxConcurrent: 5,
|
|
DefaultMaxTokens: 4096,
|
|
DefaultMaxToolCalls: 10,
|
|
DefaultMaxWallClock: 300,
|
|
}
|
|
}
|
|
|
|
// LoadTaskConfig reads task configuration from global_settings.
|
|
// Falls back to defaults for missing keys.
|
|
func LoadTaskConfig(ctx context.Context, gc store.GlobalConfigStore) TaskConfig {
|
|
cfg := DefaultTaskConfig()
|
|
if gc == nil {
|
|
return cfg
|
|
}
|
|
|
|
raw, err := gc.Get(ctx, "tasks")
|
|
if err != nil || raw == nil {
|
|
return cfg
|
|
}
|
|
|
|
if v, ok := boolVal(raw, "enabled"); ok {
|
|
cfg.Enabled = v
|
|
}
|
|
if v, ok := boolVal(raw, "allow_personal"); ok {
|
|
cfg.AllowPersonal = v
|
|
}
|
|
if v, ok := boolVal(raw, "personal_require_byok"); ok {
|
|
cfg.PersonalRequireBYOK = v
|
|
}
|
|
if v, ok := intVal(raw, "max_concurrent"); ok && v > 0 {
|
|
cfg.MaxConcurrent = v
|
|
}
|
|
if v, ok := intVal(raw, "default_max_tokens"); ok && v > 0 {
|
|
cfg.DefaultMaxTokens = v
|
|
}
|
|
if v, ok := intVal(raw, "default_max_tool_calls"); ok && v > 0 {
|
|
cfg.DefaultMaxToolCalls = v
|
|
}
|
|
if v, ok := intVal(raw, "default_max_wall_clock"); ok && v > 0 {
|
|
cfg.DefaultMaxWallClock = v
|
|
}
|
|
|
|
return cfg
|
|
}
|
|
|
|
// ApplyDefaults fills zero-value budget fields on a task with the global defaults.
|
|
func (tc TaskConfig) ApplyDefaults(t *models.Task) {
|
|
if t.MaxTokens == 0 {
|
|
t.MaxTokens = tc.DefaultMaxTokens
|
|
}
|
|
if t.MaxToolCalls == 0 {
|
|
t.MaxToolCalls = tc.DefaultMaxToolCalls
|
|
}
|
|
if t.MaxWallClock == 0 {
|
|
t.MaxWallClock = tc.DefaultMaxWallClock
|
|
}
|
|
}
|
|
|
|
// ── helpers ────────────────────────────────────
|
|
|
|
func boolVal(m models.JSONMap, key string) (bool, bool) {
|
|
v, ok := m[key]
|
|
if !ok {
|
|
return false, false
|
|
}
|
|
b, ok := v.(bool)
|
|
return b, ok
|
|
}
|
|
|
|
func intVal(m models.JSONMap, key string) (int, bool) {
|
|
v, ok := m[key]
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
// JSON numbers are float64 after Unmarshal
|
|
switch n := v.(type) {
|
|
case float64:
|
|
return int(n), true
|
|
case int:
|
|
return n, true
|
|
default:
|
|
log.Printf("[task_config] unexpected type for %s: %T", key, v)
|
|
return 0, false
|
|
}
|
|
}
|