Skip to content

Commit 6ceb997

Browse files
committed
fix(agents): correctly parse and validate min sdk version
1 parent 8011f4d commit 6ceb997

File tree

4 files changed

+165
-129
lines changed

4 files changed

+165
-129
lines changed

pkg/agentfs/sdk_version_check.go

Lines changed: 104 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"github.com/BurntSushi/toml"
1313
"github.com/Masterminds/semver/v3"
14+
"github.com/livekit/livekit-cli/v2/pkg/util"
1415
)
1516

1617
// PackageInfo represents information about a package found in a project
@@ -30,6 +31,14 @@ type VersionCheckResult struct {
3031
Error error
3132
}
3233

34+
// SourceType indicates whether we're checking a lock file or package file
35+
type SourceType int
36+
37+
const (
38+
SourceTypePackage SourceType = iota // Package file (e.g., requirements.txt, package.json)
39+
SourceTypeLock // Lock file (e.g., package-lock.json, poetry.lock)
40+
)
41+
3342
// CheckSDKVersion performs a comprehensive check for livekit-agents packages
3443
func CheckSDKVersion(dir string, projectType ProjectType, settingsMap map[string]string) error {
3544
pythonMinSDKVersion := settingsMap["python-min-sdk-version"]
@@ -86,7 +95,7 @@ func detectProjectFiles(dir string, projectType ProjectType) []string {
8695
"uv.lock",
8796
}
8897
for _, filename := range pythonFiles {
89-
if path := filepath.Join(dir, filename); fileExists(path) {
98+
if path := filepath.Join(dir, filename); util.FileExists(path) {
9099
files = append(files, path)
91100
}
92101
}
@@ -99,7 +108,7 @@ func detectProjectFiles(dir string, projectType ProjectType) []string {
99108
"bun.lockb",
100109
}
101110
for _, filename := range nodeFiles {
102-
if path := filepath.Join(dir, filename); fileExists(path) {
111+
if path := filepath.Join(dir, filename); util.FileExists(path) {
103112
files = append(files, path)
104113
}
105114
}
@@ -156,6 +165,11 @@ func parsePythonPackageVersion(line string) (string, bool) {
156165
return "latest", true
157166
}
158167

168+
// Convert Python operators to semver operators
169+
if operator == "==" {
170+
operator = "="
171+
}
172+
159173
// clean up the version string if it contains multiple constraints
160174
// handle comma-separated version constraints like ">=1.2.5,<2"
161175
if strings.Contains(version, ",") {
@@ -205,7 +219,7 @@ func checkRequirementsFile(filePath, minVersion string) VersionCheckResult {
205219

206220
version, found := parsePythonPackageVersion(line)
207221
if found {
208-
satisfied, err := isVersionSatisfied(version, minVersion)
222+
satisfied, err := isVersionSatisfied(version, minVersion, SourceTypePackage)
209223
return VersionCheckResult{
210224
PackageInfo: PackageInfo{
211225
Name: "livekit-agents",
@@ -243,7 +257,7 @@ func checkPyprojectToml(filePath, minVersion string) VersionCheckResult {
243257
if line, ok := dep.(string); ok {
244258
version, found := parsePythonPackageVersion(line)
245259
if found {
246-
satisfied, err := isVersionSatisfied(version, minVersion)
260+
satisfied, err := isVersionSatisfied(version, minVersion, SourceTypePackage)
247261
return VersionCheckResult{
248262
PackageInfo: PackageInfo{
249263
Name: "livekit-agents",
@@ -281,7 +295,7 @@ func checkPipfile(filePath, minVersion string) VersionCheckResult {
281295
version = "latest"
282296
}
283297

284-
satisfied, err := isVersionSatisfied(version, minVersion)
298+
satisfied, err := isVersionSatisfied(version, minVersion, SourceTypePackage)
285299
return VersionCheckResult{
286300
PackageInfo: PackageInfo{
287301
Name: "livekit-agents",
@@ -326,7 +340,7 @@ func checkSetupPy(filePath, minVersion string) VersionCheckResult {
326340
}
327341
version, found := parsePythonPackageVersion(packageLine)
328342
if found {
329-
satisfied, err := isVersionSatisfied(version, minVersion)
343+
satisfied, err := isVersionSatisfied(version, minVersion, SourceTypePackage)
330344
return VersionCheckResult{
331345
PackageInfo: PackageInfo{
332346
Name: "livekit-agents",
@@ -360,7 +374,7 @@ func checkSetupCfg(filePath, minVersion string) VersionCheckResult {
360374
if matches != nil {
361375
version := strings.TrimSpace(matches[2])
362376

363-
satisfied, err := isVersionSatisfied(version, minVersion)
377+
satisfied, err := isVersionSatisfied(version, minVersion, SourceTypePackage)
364378
return VersionCheckResult{
365379
PackageInfo: PackageInfo{
366380
Name: "livekit-agents",
@@ -407,7 +421,7 @@ func checkPackageJSON(filePath, minVersion string) VersionCheckResult {
407421

408422
for _, deps := range dependencyMaps {
409423
if version, ok := deps["@livekit/agents"]; ok {
410-
satisfied, err := isVersionSatisfied(version, minVersion)
424+
satisfied, err := isVersionSatisfied(version, minVersion, SourceTypePackage)
411425
return VersionCheckResult{
412426
PackageInfo: PackageInfo{
413427
Name: "@livekit/agents",
@@ -467,7 +481,7 @@ func checkPackageLockJSON(filePath, minVersion string) VersionCheckResult {
467481
}
468482

469483
if dep, ok := lockJSON.Dependencies["@livekit/agents"]; ok {
470-
satisfied, err := isVersionSatisfied(dep.Version, minVersion)
484+
satisfied, err := isVersionSatisfied(dep.Version, minVersion, SourceTypeLock)
471485
return VersionCheckResult{
472486
PackageInfo: PackageInfo{
473487
Name: "@livekit/agents",
@@ -497,7 +511,7 @@ func checkYarnLock(filePath, minVersion string) VersionCheckResult {
497511
matches := pattern.FindStringSubmatch(string(content))
498512
if matches != nil {
499513
version := matches[1]
500-
satisfied, err := isVersionSatisfied(version, minVersion)
514+
satisfied, err := isVersionSatisfied(version, minVersion, SourceTypeLock)
501515
return VersionCheckResult{
502516
PackageInfo: PackageInfo{
503517
Name: "@livekit/agents",
@@ -527,7 +541,7 @@ func checkPnpmLock(filePath, minVersion string) VersionCheckResult {
527541
matches := pattern.FindStringSubmatch(string(content))
528542
if matches != nil {
529543
version := strings.TrimSpace(matches[1])
530-
satisfied, err := isVersionSatisfied(version, minVersion)
544+
satisfied, err := isVersionSatisfied(version, minVersion, SourceTypeLock)
531545
return VersionCheckResult{
532546
PackageInfo: PackageInfo{
533547
Name: "@livekit/agents",
@@ -557,7 +571,7 @@ func checkPoetryLock(filePath, minVersion string) VersionCheckResult {
557571
matches := pattern.FindStringSubmatch(string(content))
558572
if matches != nil {
559573
version := matches[1]
560-
satisfied, err := isVersionSatisfied(version, minVersion)
574+
satisfied, err := isVersionSatisfied(version, minVersion, SourceTypeLock)
561575
return VersionCheckResult{
562576
PackageInfo: PackageInfo{
563577
Name: "livekit-agents",
@@ -587,7 +601,7 @@ func checkUvLock(filePath, minVersion string) VersionCheckResult {
587601
matches := pattern.FindStringSubmatch(string(content))
588602
if matches != nil {
589603
version := matches[1]
590-
satisfied, err := isVersionSatisfied(version, minVersion)
604+
satisfied, err := isVersionSatisfied(version, minVersion, SourceTypeLock)
591605
return VersionCheckResult{
592606
PackageInfo: PackageInfo{
593607
Name: "livekit-agents",
@@ -617,7 +631,7 @@ func checkPipfileLock(filePath, minVersion string) VersionCheckResult {
617631
matches := pattern.FindStringSubmatch(string(content))
618632
if matches != nil {
619633
version := matches[1]
620-
satisfied, err := isVersionSatisfied(version, minVersion)
634+
satisfied, err := isVersionSatisfied(version, minVersion, SourceTypeLock)
621635
return VersionCheckResult{
622636
PackageInfo: PackageInfo{
623637
Name: "livekit-agents",
@@ -636,28 +650,89 @@ func checkPipfileLock(filePath, minVersion string) VersionCheckResult {
636650
}
637651

638652
// isVersionSatisfied checks if a version satisfies the minimum requirement
639-
func isVersionSatisfied(version, minVersion string) (bool, error) {
653+
func isVersionSatisfied(version, minVersion string, sourceType SourceType) (bool, error) {
640654
// Handle special cases
641655
if version == "latest" || version == "*" || version == "" {
642656
return true, nil // Latest version always satisfies
643657
}
644658

645-
// Normalize version strings
646-
normalizedVersion := normalizeVersion(version)
647-
normalizedMin := normalizeVersion(minVersion)
659+
switch sourceType {
660+
case SourceTypeLock:
661+
// For lock files, we have the exact version that was installed
662+
// Check if this exact version is >= the minimum version
663+
normalizedVersion := normalizeVersion(version)
664+
v, err := semver.NewVersion(normalizedVersion)
665+
if err != nil {
666+
return false, fmt.Errorf("invalid version format: %s", version)
667+
}
648668

649-
// Parse versions
650-
v, err := semver.NewVersion(normalizedVersion)
651-
if err != nil {
652-
return false, fmt.Errorf("invalid version format: %s", version)
653-
}
669+
min, err := semver.NewVersion(minVersion)
670+
if err != nil {
671+
return false, fmt.Errorf("invalid minimum version format: %s", minVersion)
672+
}
654673

655-
min, err := semver.NewVersion(normalizedMin)
656-
if err != nil {
657-
return false, fmt.Errorf("invalid minimum version format: %s", minVersion)
658-
}
674+
// Check if the exact version is >= minimum version
675+
return !v.LessThan(min), nil
676+
677+
case SourceTypePackage:
678+
// For package files, we have a constraint that will be resolved at install time
679+
// Check if this constraint would allow installing a version that satisfies the minimum requirement
680+
packageConstraint, err := semver.NewConstraint(version)
681+
if err != nil {
682+
return false, fmt.Errorf("invalid package constraint format: %s", version)
683+
}
684+
685+
min, err := semver.NewVersion(minVersion)
686+
if err != nil {
687+
return false, fmt.Errorf("invalid minimum version format: %s", minVersion)
688+
}
659689

660-
return !v.LessThan(min), nil
690+
// Check if the package constraint would allow installing a version >= minimum
691+
// We do this by checking if there exists a version >= minimum that satisfies the package constraint
692+
if packageConstraint.Check(min) {
693+
// The minimum version satisfies the package constraint, so it would be installable
694+
return true, nil
695+
}
696+
697+
// Check if the package constraint allows any version >= minimum
698+
// This handles cases like ">=1.5.0" where 1.0.0 doesn't satisfy it, but it would install 1.5.0+ which > 1.0.0
699+
// We'll test a few strategic versions to see if any satisfy the package constraint and are >= minimum
700+
testVersions := []string{
701+
minVersion, // The minimum version itself
702+
fmt.Sprintf("%d.%d.%d", min.Major()+1, 0, 0),
703+
fmt.Sprintf("%d.%d.%d", min.Major(), min.Minor()+1, 0),
704+
fmt.Sprintf("%d.%d.%d", min.Major(), min.Minor(), min.Patch()+1),
705+
}
706+
707+
// Add more versions to cover edge cases
708+
if min.Major() > 0 {
709+
testVersions = append(testVersions, fmt.Sprintf("%d.0.0", min.Major()))
710+
}
711+
if min.Minor() > 0 {
712+
testVersions = append(testVersions, fmt.Sprintf("%d.%d.0", min.Major(), min.Minor()))
713+
}
714+
715+
// Add some specific versions that might be common in constraints
716+
testVersions = append(testVersions,
717+
fmt.Sprintf("%d.%d.0", min.Major(), min.Minor()+2),
718+
fmt.Sprintf("%d.%d.0", min.Major(), min.Minor()+5),
719+
fmt.Sprintf("%d.%d.0", min.Major(), min.Minor()+10),
720+
)
721+
722+
for _, testVersion := range testVersions {
723+
if v, err := semver.NewVersion(testVersion); err == nil {
724+
// Check if this version is >= minimum and satisfies the package constraint
725+
if !v.LessThan(min) && packageConstraint.Check(v) {
726+
return true, nil
727+
}
728+
}
729+
}
730+
731+
return false, nil
732+
733+
default:
734+
return false, fmt.Errorf("unknown source type: %d", sourceType)
735+
}
661736
}
662737

663738
// normalizeVersion normalizes version strings for semver parsing
@@ -666,10 +741,7 @@ func normalizeVersion(version string) string {
666741
version = strings.TrimSpace(version)
667742
version = strings.Trim(version, " \"'")
668743

669-
// Remove version specifiers that aren't part of the version itself
670-
version = regexp.MustCompile(`^[=~><!]+`).ReplaceAllString(version, "")
671-
672-
// Handle npm version ranges
744+
// Handle npm version ranges (^ and ~ are npm-specific, not semver constraints)
673745
if strings.HasPrefix(version, "^") || strings.HasPrefix(version, "~") {
674746
version = version[1:]
675747
}
@@ -732,9 +804,3 @@ func getTargetPackageName(projectType ProjectType) string {
732804
return ""
733805
}
734806
}
735-
736-
// fileExists checks if a file exists
737-
func fileExists(path string) bool {
738-
_, err := os.Stat(path)
739-
return err == nil
740-
}

0 commit comments

Comments
 (0)