feat: dumb
This commit is contained in:
@@ -0,0 +1 @@
|
||||
"""Extension runtime contracts."""
|
||||
@@ -0,0 +1,8 @@
|
||||
from .catalog import get_extension_capability_registry
|
||||
from .registry import CapabilityDefinition, CapabilityRegistry
|
||||
|
||||
__all__ = [
|
||||
"CapabilityDefinition",
|
||||
"CapabilityRegistry",
|
||||
"get_extension_capability_registry",
|
||||
]
|
||||
@@ -0,0 +1,120 @@
|
||||
from .models import (
|
||||
CreateInvoiceRequest,
|
||||
CreateInvoiceResponse,
|
||||
EmptyRequest,
|
||||
KvGetRequest,
|
||||
KvGetResponse,
|
||||
KvListRequest,
|
||||
KvListResponse,
|
||||
KvSetRequest,
|
||||
KvSetResponse,
|
||||
LogRequest,
|
||||
LogResponse,
|
||||
NowResponse,
|
||||
RandomIdRequest,
|
||||
RandomIdResponse,
|
||||
WatchPaymentRequest,
|
||||
WatchPaymentResponse,
|
||||
)
|
||||
from .registry import CapabilityDefinition, CapabilityRegistry
|
||||
|
||||
|
||||
def get_extension_capability_registry() -> CapabilityRegistry:
|
||||
registry = CapabilityRegistry()
|
||||
|
||||
for capability in [
|
||||
CapabilityDefinition(
|
||||
id="storage.get",
|
||||
namespace="storage",
|
||||
name="Get storage value",
|
||||
host_name="kv_get",
|
||||
sdk_name="get",
|
||||
description="Read one value from the extension storage namespace.",
|
||||
request_model=KvGetRequest,
|
||||
response_model=KvGetResponse,
|
||||
required_permission="ext.storage.read_write",
|
||||
public_context=True,
|
||||
),
|
||||
CapabilityDefinition(
|
||||
id="storage.set",
|
||||
namespace="storage",
|
||||
name="Set storage value",
|
||||
host_name="kv_set",
|
||||
sdk_name="set",
|
||||
description="Write one value to the extension storage namespace.",
|
||||
request_model=KvSetRequest,
|
||||
response_model=KvSetResponse,
|
||||
required_permission="ext.storage.read_write",
|
||||
),
|
||||
CapabilityDefinition(
|
||||
id="storage.list",
|
||||
namespace="storage",
|
||||
name="List storage keys",
|
||||
host_name="kv_list",
|
||||
sdk_name="list",
|
||||
description="List keys under a prefix in the extension storage namespace.",
|
||||
request_model=KvListRequest,
|
||||
response_model=KvListResponse,
|
||||
required_permission="ext.storage.read_write",
|
||||
public_context=True,
|
||||
),
|
||||
CapabilityDefinition(
|
||||
id="wallet.create_invoice",
|
||||
namespace="wallet",
|
||||
name="Create invoice",
|
||||
host_name="create_invoice",
|
||||
sdk_name="createInvoice",
|
||||
description="Create an incoming Lightning invoice for an allowed wallet.",
|
||||
request_model=CreateInvoiceRequest,
|
||||
response_model=CreateInvoiceResponse,
|
||||
required_permission="wallet.create_invoice",
|
||||
public_context=True,
|
||||
),
|
||||
CapabilityDefinition(
|
||||
id="payments.watch",
|
||||
namespace="payments",
|
||||
name="Watch payment",
|
||||
host_name="watch_payment",
|
||||
sdk_name="watch",
|
||||
description="Subscribe the extension to a payment state callback.",
|
||||
request_model=WatchPaymentRequest,
|
||||
response_model=WatchPaymentResponse,
|
||||
required_permission="payments.watch",
|
||||
),
|
||||
CapabilityDefinition(
|
||||
id="system.random_id",
|
||||
namespace="system",
|
||||
name="Random ID",
|
||||
host_name="random_id",
|
||||
sdk_name="id",
|
||||
description="Create a random extension-local identifier.",
|
||||
request_model=RandomIdRequest,
|
||||
response_model=RandomIdResponse,
|
||||
public_context=True,
|
||||
),
|
||||
CapabilityDefinition(
|
||||
id="system.now",
|
||||
namespace="system",
|
||||
name="Current timestamp",
|
||||
host_name="now",
|
||||
sdk_name="now",
|
||||
description="Return the current Unix timestamp.",
|
||||
request_model=EmptyRequest,
|
||||
response_model=NowResponse,
|
||||
public_context=True,
|
||||
),
|
||||
CapabilityDefinition(
|
||||
id="system.log",
|
||||
namespace="system",
|
||||
name="Log message",
|
||||
host_name="log",
|
||||
sdk_name="log",
|
||||
description="Write a bounded message to the extension log.",
|
||||
request_model=LogRequest,
|
||||
response_model=LogResponse,
|
||||
public_context=True,
|
||||
),
|
||||
]:
|
||||
registry.register(capability)
|
||||
|
||||
return registry
|
||||
@@ -0,0 +1,76 @@
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class EmptyRequest(BaseModel):
|
||||
pass
|
||||
|
||||
|
||||
class KvGetRequest(BaseModel):
|
||||
key: str = Field(..., min_length=1, max_length=512)
|
||||
|
||||
|
||||
class KvGetResponse(BaseModel):
|
||||
value: str | None = None
|
||||
|
||||
|
||||
class KvSetRequest(BaseModel):
|
||||
key: str = Field(..., min_length=1, max_length=512)
|
||||
value: str = Field(..., max_length=65536)
|
||||
|
||||
|
||||
class KvSetResponse(BaseModel):
|
||||
ok: bool = True
|
||||
|
||||
|
||||
class KvListRequest(BaseModel):
|
||||
prefix: str = Field(..., min_length=1, max_length=512)
|
||||
|
||||
|
||||
class KvListResponse(BaseModel):
|
||||
keys: list[str] = []
|
||||
|
||||
|
||||
class CreateInvoiceRequest(BaseModel):
|
||||
wallet_id: str = Field(..., min_length=1, max_length=128)
|
||||
amount_sat: int = Field(..., gt=0)
|
||||
memo: str = Field(..., max_length=512)
|
||||
tag: str = Field(..., min_length=1, max_length=64)
|
||||
extra: dict[str, str] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class CreateInvoiceResponse(BaseModel):
|
||||
payment_hash: str
|
||||
payment_request: str
|
||||
checking_id: str
|
||||
|
||||
|
||||
class WatchPaymentRequest(BaseModel):
|
||||
payment_hash: str = Field(..., min_length=1, max_length=128)
|
||||
callback_export: str = Field(..., min_length=1, max_length=128)
|
||||
|
||||
|
||||
class WatchPaymentResponse(BaseModel):
|
||||
ok: bool = True
|
||||
|
||||
|
||||
class RandomIdRequest(BaseModel):
|
||||
prefix: str = Field(..., min_length=1, max_length=32)
|
||||
|
||||
|
||||
class RandomIdResponse(BaseModel):
|
||||
id: str
|
||||
|
||||
|
||||
class NowResponse(BaseModel):
|
||||
timestamp: int
|
||||
|
||||
|
||||
class LogRequest(BaseModel):
|
||||
level: Literal["debug", "info", "warning", "error"] = "info"
|
||||
message: str = Field(..., min_length=1, max_length=2048)
|
||||
|
||||
|
||||
class LogResponse(BaseModel):
|
||||
ok: bool = True
|
||||
@@ -0,0 +1,76 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CapabilityDefinition:
|
||||
id: str
|
||||
namespace: str
|
||||
name: str
|
||||
host_name: str
|
||||
sdk_name: str
|
||||
description: str
|
||||
request_model: type[BaseModel]
|
||||
response_model: type[BaseModel]
|
||||
required_permission: str | None = None
|
||||
public_context: bool = False
|
||||
|
||||
@property
|
||||
def sdk_qualified_name(self) -> str:
|
||||
return f"{self.namespace}.{self.sdk_name}"
|
||||
|
||||
def request_schema(self) -> dict[str, Any]:
|
||||
return self.request_model.schema(ref_template="#/definitions/{model}")
|
||||
|
||||
def response_schema(self) -> dict[str, Any]:
|
||||
return self.response_model.schema(ref_template="#/definitions/{model}")
|
||||
|
||||
def to_codegen_contract(self) -> dict[str, Any]:
|
||||
return {
|
||||
"id": self.id,
|
||||
"namespace": self.namespace,
|
||||
"name": self.name,
|
||||
"host_name": self.host_name,
|
||||
"sdk_name": self.sdk_name,
|
||||
"sdk_qualified_name": self.sdk_qualified_name,
|
||||
"description": self.description,
|
||||
"required_permission": self.required_permission,
|
||||
"public_context": self.public_context,
|
||||
"request_schema": self.request_schema(),
|
||||
"response_schema": self.response_schema(),
|
||||
}
|
||||
|
||||
|
||||
class CapabilityRegistry:
|
||||
def __init__(self) -> None:
|
||||
self._capabilities: dict[str, CapabilityDefinition] = {}
|
||||
|
||||
def register(self, capability: CapabilityDefinition) -> None:
|
||||
if capability.id in self._capabilities:
|
||||
raise ValueError(f"Capability '{capability.id}' already registered.")
|
||||
self._capabilities[capability.id] = capability
|
||||
|
||||
def get(self, capability_id: str) -> CapabilityDefinition | None:
|
||||
return self._capabilities.get(capability_id)
|
||||
|
||||
def require(self, capability_id: str) -> CapabilityDefinition:
|
||||
capability = self.get(capability_id)
|
||||
if not capability:
|
||||
raise KeyError(f"Unknown capability '{capability_id}'.")
|
||||
return capability
|
||||
|
||||
def all(self) -> list[CapabilityDefinition]:
|
||||
return sorted(self._capabilities.values(), key=lambda c: c.id)
|
||||
|
||||
def by_permission(self, permission_id: str) -> list[CapabilityDefinition]:
|
||||
return [c for c in self.all() if c.required_permission == permission_id]
|
||||
|
||||
def to_codegen_contract(self) -> dict[str, Any]:
|
||||
return {
|
||||
"version": 1,
|
||||
"capabilities": [c.to_codegen_contract() for c in self.all()],
|
||||
}
|
||||
Reference in New Issue
Block a user