package pluginsimport ("regexp""slices""github.com/navidrome/navidrome/core/agents")// Capability represents a plugin capability type.// Capabilities are detected by checking which functions a plugin exports.type Capability string// capabilityFunctions maps each capability to its required/optional functions.// A plugin has a capability if it exports at least one of these functions.var capabilityFunctions = map[Capability][]string{}// registerCapability registers a capability with its associated functions.func registerCapability(cap Capability, functions ...string) {capabilityFunctions[cap] = functions}// functionExistsChecker is an interface for checking if a function exists in a plugin.// This allows for testing without a real plugin instance.type functionExistsChecker interface {FunctionExists(name string) bool}// detectCapabilities detects which capabilities a plugin has by checking// which functions it exports.func detectCapabilities(plugin functionExistsChecker) []Capability {var capabilities []Capabilityfor cap, functions := range capabilityFunctions {if slices.ContainsFunc(functions, plugin.FunctionExists) {capabilities = append(capabilities, cap) // Found at least one function, plugin has this capability}}return capabilities}// hasCapability checks if the given capabilities slice contains a specific capability.func hasCapability(capabilities []Capability, cap Capability) bool {return slices.Contains(capabilities, cap)}// retryLaterRe matches one capability's `<capability>(retry_later[:seconds])` token, which is// all a plugin fault carries back across the WASM boundary. The capability is part of the// pattern, so another capability's token in the same message cannot mask this one. The leading// \b keeps a superstring like `useragent(retry_later)` from matching `agent`.var (agentRetryLaterRe = retryLaterRe("agent")scrobblerRetryLaterRe = retryLaterRe("scrobbler"))func retryLaterRe(capability string) *regexp.Regexp {return regexp.MustCompile(`\b` + capability + `\(retry_later(?::(\d+))?\)`)}// parseRetryLater reports whether msg carries the capability's retry_later token, with its delay.func parseRetryLater(re *regexp.Regexp, msg string) (*agents.RetryLaterError, bool) {m := re.FindStringSubmatch(msg)if m == nil {return nil, false}return &agents.RetryLaterError{RetryIn: agents.ParseRetryIn(m[1])}, true}