Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

128 lines
4.1 KiB
Go

package agentstatus
import (
"strconv"
"strings"
"github.com/tutti-os/tutti/packages/agent/daemon/managednpm"
"github.com/tutti-os/tutti/packages/agent/daemon/providerregistry"
)
// MinSupportedCodexVersion is the lowest Codex CLI version Tutti supports.
//
// Capability-derived: 0.126.0 is the release that introduced the newest
// app-server method our codex runtime integrates (the thread/goal API); every
// other app-server capability we call landed earlier, and the enrichment ones
// (account/models/rateLimits/collaborationMode/goal) degrade gracefully below
// their version. So 0.126.0 is the floor at which the full app-server feature
// set we wire up is present.
//
// Single tunable hard gate: a detected codex below this floor is flagged as
// too old (surfaced as CODEX_VERSION_TOO_OLD) and the server-side 400 is the
// backstop. Bump providerregistry.CodexMinVersion when raising the floor;
// nothing else needs to change.
const MinSupportedCodexVersion = providerregistry.CodexMinVersion
// compareCLIVersions compares two semver-ish CLI version strings.
//
// It returns -1, 0, or 1 (a<b, a==b, a>b) and ok=true when both parse. A
// leading "v" is tolerated and missing components default to 0. A pre-release
// suffix (after "-") sorts below the same release core. ok is false when either
// version cannot be parsed into a numeric core.
func compareCLIVersions(a, b string) (int, bool) {
coreA, preA, okA := parseCLIVersionForComparison(a)
coreB, preB, okB := parseCLIVersionForComparison(b)
if !okA || !okB {
return 0, false
}
for i := 0; i < 3; i++ {
if coreA[i] != coreB[i] {
if coreA[i] < coreB[i] {
return -1, true
}
return 1, true
}
}
// Equal cores: a pre-release is lower than the release.
switch {
case preA && !preB:
return -1, true
case !preA && preB:
return 1, true
default:
return 0, true
}
}
// codexVersionMeetsMinimum reports whether version satisfies
// MinSupportedCodexVersion. An empty or unparseable version is treated as
// "unknown" and is NOT flagged as too old here — binary/CLI presence checks
// cover the missing case, and the server-side error is the backstop.
func codexVersionMeetsMinimum(version string) bool {
return cliVersionMeetsMinimumAllowUnknown(version, MinSupportedCodexVersion)
}
// cliVersionMeetsMinimumAllowUnknown preserves the historical Codex behavior:
// an unavailable version is left to the app-server compatibility backstop.
func cliVersionMeetsMinimumAllowUnknown(version string, minimum string) bool {
minimum = strings.TrimSpace(minimum)
if minimum == "" {
return true
}
cmp, ok := compareCLIVersions(version, minimum)
if !ok {
return true
}
return cmp >= 0
}
// providerCLIVersionMeetsMinimum applies the descriptor-owned floor. Generic
// providers fail closed when their version is unavailable; Codex retains its
// established unknown-version compatibility behavior above.
func providerCLIVersionMeetsMinimum(spec ProviderSpec, version string) bool {
minimum := strings.TrimSpace(spec.MinVersion)
if minimum == "" {
return true
}
if providerUsesManagedNPMVersionContract(spec) {
return managednpm.DecideVersion(version, minimum) == managednpm.VersionDecisionReady
}
cmp, ok := compareCLIVersions(version, minimum)
if !ok {
return isCodexStatusSpec(spec)
}
return cmp >= 0
}
func providerUsesManagedNPMVersionContract(spec ProviderSpec) bool {
return !isCodexStatusSpec(spec) && spec.Install.Kind == InstallerKindManagedNPMPackage
}
// parseCLIVersionForComparison returns the [major, minor, patch] core, whether a
// pre-release suffix is present, and ok when the core parsed.
func parseCLIVersionForComparison(version string) ([3]int, bool, bool) {
v := strings.TrimSpace(version)
v = strings.TrimPrefix(v, "v")
if v == "" {
return [3]int{}, false, false
}
pre := false
if idx := strings.IndexAny(v, "-+"); idx >= 0 {
pre = v[idx] == '-'
v = v[:idx]
}
parts := strings.Split(v, ".")
if len(parts) == 0 || len(parts) > 3 {
return [3]int{}, false, false
}
var core [3]int
for i := 0; i < len(parts); i++ {
n, err := strconv.Atoi(parts[i])
if err != nil || n < 0 {
return [3]int{}, false, false
}
core[i] = n
}
return core, pre, true
}