87 lines
2.3 KiB
Go
87 lines
2.3 KiB
Go
|
|
package secutools
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"strings"
|
||
|
|
)
|
||
|
|
|
||
|
|
// DecideProvider inspects a frontmatter map (as decoded from YAML) and
|
||
|
|
// returns the provider string that should handle the task. Valid return
|
||
|
|
// values:
|
||
|
|
//
|
||
|
|
// - "local" — no delegation, dispatch on a Claude Code session (Phase 1)
|
||
|
|
// - "claude-code" — explicit local dispatch (alias of "local")
|
||
|
|
// - "gpu" — delegate to secutools with preferred_ai=gpu
|
||
|
|
// - "gemini" — delegate to secutools with preferred_ai=gemini
|
||
|
|
// - "claude-api" — delegate to secutools with preferred_ai=claude-api
|
||
|
|
// - "auto" — delegate to secutools, let smart_triage choose
|
||
|
|
//
|
||
|
|
// Precedence:
|
||
|
|
// 1. needs_claude_code: true → "local"
|
||
|
|
// 2. preferred_ai in {claude-code} → "local"
|
||
|
|
// 3. allow_delegation == false / missing → "local"
|
||
|
|
// 4. preferred_ai in {gpu,gemini,claude-api} → that provider
|
||
|
|
// 5. preferred_ai in {"", auto} → "auto"
|
||
|
|
// 6. unknown preferred_ai → "local" (fail-safe)
|
||
|
|
//
|
||
|
|
// This function is intentionally permissive on input types: YAML booleans
|
||
|
|
// may decode as bool, strings as string. It coerces common forms and
|
||
|
|
// returns "local" on malformed input rather than panicking.
|
||
|
|
func DecideProvider(fm map[string]any) string {
|
||
|
|
if fm == nil {
|
||
|
|
return "local"
|
||
|
|
}
|
||
|
|
|
||
|
|
if asBool(fm["needs_claude_code"]) {
|
||
|
|
return "local"
|
||
|
|
}
|
||
|
|
|
||
|
|
pref := strings.ToLower(strings.TrimSpace(asString(fm["preferred_ai"])))
|
||
|
|
if pref == "claude-code" || pref == "local" {
|
||
|
|
return "local"
|
||
|
|
}
|
||
|
|
|
||
|
|
if !asBool(fm["allow_delegation"]) {
|
||
|
|
return "local"
|
||
|
|
}
|
||
|
|
|
||
|
|
switch pref {
|
||
|
|
case "gpu", "gemini", "claude-api":
|
||
|
|
return pref
|
||
|
|
case "", "auto":
|
||
|
|
return "auto"
|
||
|
|
default:
|
||
|
|
// Unknown provider — fail safe to local.
|
||
|
|
return "local"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// asBool accepts bool, "true"/"false", "1"/"0". Defaults to false.
|
||
|
|
func asBool(v any) bool {
|
||
|
|
switch t := v.(type) {
|
||
|
|
case bool:
|
||
|
|
return t
|
||
|
|
case string:
|
||
|
|
s := strings.ToLower(strings.TrimSpace(t))
|
||
|
|
return s == "true" || s == "1" || s == "yes"
|
||
|
|
case int:
|
||
|
|
return t != 0
|
||
|
|
default:
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// asString coerces v to a trimmed string. Returns "" for nil/unknown types.
|
||
|
|
func asString(v any) string {
|
||
|
|
switch t := v.(type) {
|
||
|
|
case string:
|
||
|
|
return t
|
||
|
|
case fmt.Stringer:
|
||
|
|
return t.String()
|
||
|
|
case nil:
|
||
|
|
return ""
|
||
|
|
default:
|
||
|
|
return fmt.Sprintf("%v", t)
|
||
|
|
}
|
||
|
|
}
|