Files
agame/addons/godot_ai/utils/server_version_check.gd
2026-09-21 16:16:59 +08:00

65 lines
2.5 KiB
GDScript

@tool
class_name McpServerVersionCheck
extends RefCounted
## Published v4 compatibility value. Lifecycle owns the authenticated
## handshake and episode transition; this class intentionally owns no timer,
## connection, or manager reference.
## First release whose attached bridge follows servers of the same major.
const FIRST_BRIDGE_TOLERANT_VERSION := "4.0.4"
## Whether clients attached at `from_version` can reconnect after updating
## to `to_version` without restarting their bridge (#1024).
static func attached_bridges_follow(from_version: String, to_version: String) -> bool:
var from_tuple := version_tuple(from_version)
var to_tuple := version_tuple(to_version)
if from_tuple.is_empty() or to_tuple.is_empty():
return false
if int(from_tuple[0]) != int(to_tuple[0]):
return false
return compare(from_tuple, version_tuple(FIRST_BRIDGE_TOLERANT_VERSION)) >= 0
## Leading numeric `major.minor.patch` of a version as `[major, minor, patch]`,
## or `[]` when it does not start that way (a dev build, a malformed pin).
## A suffix after the patch (`4.0.3+local.1`, `4.1.0-rc1`) is ignored; a
## dangling separator (`4.0.3+`) is not a version.
static func version_tuple(version: String) -> Array:
var regex := RegEx.create_from_string("^(\\d+)\\.(\\d+)\\.(\\d+)(?:$|[.+-][0-9A-Za-z][0-9A-Za-z.+-]*$)")
var found := regex.search(version.strip_edges())
if found == null:
return []
return [int(found.get_string(1)), int(found.get_string(2)), int(found.get_string(3))]
static func compare(a: Array, b: Array) -> int:
for index in range(3):
if int(a[index]) != int(b[index]):
return -1 if int(a[index]) < int(b[index]) else 1
return 0
## `candidate` is a parseable version older than `reference` within the same
## major version. Unparseable, equal, newer, or another major all read false:
## a caller may only replace what it can prove is older than itself.
static func is_older_same_major(candidate: String, reference: String) -> bool:
var candidate_tuple := version_tuple(candidate)
var reference_tuple := version_tuple(reference)
if candidate_tuple.is_empty() or reference_tuple.is_empty():
return false
if int(candidate_tuple[0]) != int(reference_tuple[0]):
return false
return compare(candidate_tuple, reference_tuple) < 0
static func evaluate(actual_version: String, expected_version: String) -> Dictionary:
if actual_version.is_empty():
return {"compatible": false, "reason": "missing_version"}
var compatible := actual_version == expected_version
return {
"compatible": compatible,
"reason": "" if compatible else "version_mismatch",
}