metadata — PluginMetadata, SchemaProperty, ExecutionContext, Enums

On this page

Overview

This module defines the data model that makes plugins discoverable and callable by an LLM: rich, structured metadata (PluginMetadata), a JSON-Schema-compatible property descriptor (SchemaProperty), the runtime context threaded through every execution (ExecutionContext), and four enumerations used across the rest of the package.


Enumerations

PluginStatus(str, Enum)

Python · 7 lines
DISABLED = "disabled"
ENABLED = "enabled"
LOADING = "loading"
ERROR = "error"
UPDATING = "updating"
DEPRECATED = "deprecated"
INITIALIZING = "initializing"

Lifecycle state of a BasePlugin instance. Set by PluginManager._initialize_plugin (initializing → enabled/error) and automatically by BasePlugin._record_failure (→ error, once failures exceed 10 and the failure rate is over 50%).

PluginPriority(int, Enum)

Python · 5 lines
CRITICAL = 1000
HIGH = 100
NORMAL = 50
LOW = 10
BACKGROUND = 1

An integer-valued priority scale. Note: as of this source snapshot, PluginPriority is defined and exported but not referenced anywhere else in the package's execution or selection logic — it appears to be a reserved extension point rather than an active behavior. PluginManager.on() has its own separate integer priority parameter for hook ordering, unrelated to this enum.

PermissionType(str, Enum)

Python · 8 lines
FILE_READ = "file_read"
FILE_WRITE = "file_write"
NETWORK_ACCESS = "network_access"
SYSTEM_CALL = "system_call"
DATABASE_ACCESS = "database_access"
USER_DATA_ACCESS = "user_data_access"
LLM_CALL = "llm_call"
VECTOR_STORE = "vector_store"

The complete, closed set of permission kinds understood by PermissionGuard. Permissions are declared per-plugin in PluginMetadata.permissions (as plain strings) and enforced at grant/check time.

CostTier(str, Enum)

Python · 4 lines
FREE = "free"           # no external calls
CHEAP = "cheap"          # < $0.001 per call
MODERATE = "moderate"     # $0.001 – $0.01
EXPENSIVE = "expensive"    # > $0.01

Used by AIPluginSelector.select(max_cost=...) to filter candidates, and folded into the LLM-facing description text via PluginMetadata._build_llm_description().


SchemaProperty

Python · 10 lines
@dataclass
class SchemaProperty:
    name: str
    type: Literal["string", "integer", "number", "boolean", "array", "object"]
    description: str
    required: bool = True
    enum: Optional[List[Any]] = None
    default: Optional[Any] = None
    items: Optional[Dict[str, Any]] = None    # for type="array"
    properties: Optional[Dict[str, Any]] = None  # for type="object"

Describes one field of a plugin's input_schema or output_schema. Compatible with JSON Schema draft-07 and OpenAI function-calling parameter format.

to_json_schema() -> Dict[str, Any]

Builds {"type": ..., "description": ...}, then conditionally adds enum, default, items, and properties keys only when they are not None. This is a pure function with no side effects and is used by both PluginMetadata.to_tool_descriptor() and to_anthropic_tool().

Example:

Python · 6 lines
prop = SchemaProperty(
    name="top_k", type="integer", description="Number of results",
    required=False, default=5,
)
prop.to_json_schema()
# {"type": "integer", "description": "Number of results", "default": 5}

PluginMetadata

Python · 22 lines
@dataclass
class PluginMetadata:
    name: str
    version: str
    author: str
    description: str
    capabilities: List[str] = field(default_factory=list)
    tags: List[str] = field(default_factory=list)
    use_cases: List[str] = field(default_factory=list)
    cost_tier: CostTier = CostTier.FREE
    cost_per_call_usd: float = 0.0
    input_schema: List[SchemaProperty] = field(default_factory=list)
    output_schema: List[SchemaProperty] = field(default_factory=list)
    dependencies: List[str] = field(default_factory=list)
    required_hooks: List[str] = field(default_factory=list)
    permissions: List[str] = field(default_factory=list)
    min_system_version: str = "1.0.0"
    max_system_version: str = "999.0.0"
    homepage: str = ""
    license: str = "MIT"
    created_at: Optional[datetime] = None
    updated_at: Optional[datetime] = None

__post_init__ validation

Runs automatically on construction. Raises ValueError if:

  • name is empty or whitespace-only.
  • version does not match ^\d+\.\d+\.\d+$ (strict MAJOR.MINOR.PATCH, no pre-release/build suffixes allowed — "2.0.0-beta" would fail).
  • description is empty or whitespace-only.
  • author is empty or whitespace-only.

If valid, created_at/updated_at default to datetime.utcnow() when not supplied.

to_tool_descriptor() -> Dict[str, Any]

Builds an OpenAI function-calling descriptor:

Python · 12 lines
{
  "type": "function",
  "function": {
    "name": self.name,
    "description": self._build_llm_description(),
    "parameters": {
      "type": "object",
      "properties": {...},   # from input_schema via SchemaProperty.to_json_schema()
      "required": [...],     # names of input_schema entries with required=True
    },
  },
}

to_anthropic_tool() -> Dict[str, Any]

Same field derivation, Anthropic's flatter shape:

Python · 5 lines
{
  "name": self.name,
  "description": self._build_llm_description(),
  "input_schema": {"type": "object", "properties": {...}, "required": [...]},
}

_build_llm_description() -> str

Joins, with " | ": the base description; "Use cases: " + "; ".join(use_cases) if any use cases are declared; and f"Cost: {cost_tier.value} (~${cost_per_call_usd:.4f}/call)" if cost_tier is not CostTier.FREE.

is_compatible_with(system_version: str) -> bool

Returns True iff system_version >= min_system_version and system_version <= max_system_version, using _compare_versions (dotted-integer comparison, not full semver — see below).

to_dict() / from_dict(data)

Round-trip JSON-serializable representation. to_dict() converts cost_tier to its .value and datetimes to ISO strings; from_dict() reverses both conversions. Useful for persisting plugin manifests as JSON/YAML.

Raises

  • ValueError — from __post_init__, on any of the four validation failures above.

ExecutionContext

Python · 11 lines
@dataclass
class ExecutionContext:
    query: str = ""
    session_id: str = ""
    user_id: str = ""
    metadata: Dict[str, Any] = field(default_factory=dict)
    memory: Optional[Any] = None
    cache: Optional[Any] = None
    router: Optional[Any] = None
    trace_id: str = ""
    max_tokens: int = 2048

Passed as the second argument to every execute() call. memory, cache, and router are untyped handles — the framework threads them through but never constructs or interprets them; your application is responsible for what these objects are.

PermissionGuard.check() reads context.session_id/context.user_id to enforce session/user-scoped permission grants.


Helper functions (module-private, documented for completeness)

  • _valid_version(v: str) -> bool — bool(re.match(r"^\d+\.\d+\.\d+$", v or "")).
  • _compare_versions(v1, v2) -> int — splits each string on ., casts to int, compares component-wise, returns -1/0/1 on the first differing component (or 0 if all compared components are equal — note this uses zip(), so "1.2" vs "1.2.0" would compare equal on their two common components without checking the third).

Complete examples

1. Minimal metadata:

Python · 3 lines
from zerda.plugins import PluginMetadata

meta = PluginMetadata(name="ping", version="1.0.0", author="me", description="Health check.")

2. Full metadata with schema, for LLM tool use:

Python · 18 lines
from zerda.plugins import PluginMetadata, SchemaProperty, CostTier

meta = PluginMetadata(
    name="web_search",
    version="1.2.0",
    author="fennec-team",
    description="Searches the web and returns ranked results.",
    capabilities=["search", "retrieval"],
    tags=["web", "search"],
    use_cases=["Find recent news", "Look up facts"],
    cost_tier=CostTier.CHEAP,
    cost_per_call_usd=0.0005,
    input_schema=[
        SchemaProperty(name="query", type="string", description="Search query"),
        SchemaProperty(name="top_k", type="integer", description="Max results", required=False, default=5),
    ],
    permissions=["network_access"],
)

3. Error handling — invalid version:

Python · 4 lines
try:
    PluginMetadata(name="x", version="v1", author="a", description="d")
except ValueError as e:
    print(e)  # "Invalid semantic version: 'v1'"

4. Exporting for OpenAI / Anthropic:

Python · 2 lines
tool_openai = meta.to_tool_descriptor()
tool_claude = meta.to_anthropic_tool()

5. Compatibility check before manual registration:

Python · 2 lines
if meta.is_compatible_with("1.0.0"):
    ...

Best practices

  • Always set permissions to the minimal set your plugin actually needs — PluginManager.startup() grants exactly (and only) what's declared here when safe_mode=False.
  • Use use_cases liberally; it directly improves AIPluginSelector keyword-match quality.
  • Keep version strict MAJOR.MINOR.PATCH — pre-release suffixes will raise ValueError.

Common mistakes

  • Passing a non-dict custom to PluginConfig expecting it to merge with defaults — it replaces the default {} entirely.
  • Assuming _compare_versions implements full semver ordering (pre-release tags, build metadata) — it only compares dotted integers.

Related pages