#![cfg_attr(feature = "fail-on-warnings", deny(warnings))]
#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
#![allow(clippy::multiple_crate_versions)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::large_enum_variant)]
#![allow(clippy::struct_excessive_bools)]
#![allow(clippy::not_unsafe_ptr_arg_deref)]
#![allow(clippy::result_large_err)]
#![allow(clippy::unsafe_derive_deserialize)]
pub mod action_dispatch;
mod capability;
mod command;
mod context;
mod error;
mod event;
mod host;
mod host_services;
mod ident;
mod native_exports;
mod process_runtime;
pub mod prompt;
mod ready;
mod service;
mod stateful_plugin;
pub mod typed_dispatch;
mod typed_dispatch_client;
mod version;
mod wire_event_sink;
pub use bmux_perf_telemetry as perf_telemetry;
pub use capability::{HostScope, PluginFeature};
pub use command::{
CommandExecutionKind, PluginCommand, PluginCommandArgument, PluginCommandArgumentKind,
};
pub use context::{
ActiveKeybinding, CORE_CLI_BRIDGE_MAGIC_V1, CORE_CLI_BRIDGE_PROTOCOL_V1,
CORE_CLI_COMMAND_CAPABILITY, CORE_CLI_COMMAND_INTERFACE_V1,
CORE_CLI_COMMAND_RUN_PATH_OPERATION_V1, CORE_CLI_COMMAND_RUN_PLUGIN_OPERATION_V1,
CoreCliCommandRequest, CoreCliCommandResponse, HostKernelBridge, HostKernelBridgeRequest,
HostKernelBridgeResponse, NativeCommandContext, NativeCommandInvocationSource,
NativeLifecycleContext, NativeServiceContext, PLUGIN_CLI_BRIDGE_MAGIC_V1,
PluginCliCommandRequest, PluginCliCommandResponse, RegisteredPluginInfo,
decode_host_kernel_bridge_cli_command_payload,
decode_host_kernel_bridge_plugin_command_payload,
encode_host_kernel_bridge_cli_command_payload,
encode_host_kernel_bridge_plugin_command_payload,
};
pub use error::{CapabilityAccessDeniedHint, PluginError, Result};
pub use event::{
PluginEvent, PluginEventDelivery, PluginEventPayload, PluginEventPublication,
PluginEventSubscription,
};
pub use host::{HostConnectionInfo, HostMetadata, PluginContext, PluginHost, ResolvedService};
pub use host_services::{
COMMAND_OUTCOME_STATUS_MESSAGE_KEY, LogWriteLevel, LogWriteRequest, PluginCommandOutcome,
RecordingWriteEventRequest, RecordingWriteEventResponse, StorageGetRequest, StorageGetResponse,
StorageKey, StorageKeyError, StorageSetRequest, VolatileStateClearRequest,
VolatileStateGetRequest, VolatileStateGetResponse, VolatileStateSetRequest,
begin_command_outcome_capture, finish_command_outcome_capture, record_command_outcome_metadata,
storage_key_is_valid,
};
pub use ident::{CapabilityId, InterfaceId, OperationId, PluginEventKind};
#[macro_export]
macro_rules! storage_key {
($key:literal) => {{
const _: () = assert!(
$crate::storage_key_is_valid($key),
"invalid storage key literal; use non-empty [A-Za-z0-9._-]"
);
$crate::StorageKey::from_validated_literal($key)
}};
}
pub use native_exports::{
EXIT_ERROR, EXIT_OK, EXIT_UNAVAILABLE, EXIT_USAGE, HostAsyncHandle, PluginCommandError,
RustPlugin, TypedServiceRegistrationContext, take_last_command_error,
};
pub use process_runtime::{
PROCESS_RUNTIME_ENV_PERSISTENT_WORKER, PROCESS_RUNTIME_ENV_PLUGIN_ID,
PROCESS_RUNTIME_ENV_PROTOCOL, PROCESS_RUNTIME_MAGIC_V1, PROCESS_RUNTIME_PROTOCOL_V1,
PROCESS_RUNTIME_TRANSPORT_STDIO_V1, ProcessInvocationRequest, ProcessInvocationResponse,
decode_process_invocation_response, decode_process_runtime_frame,
encode_process_invocation_request, encode_process_runtime_frame,
};
pub use ready::{ReadySignalDecl, ReadyStatus, ReadyTracker};
pub use service::{
CURRENT_SERVICE_PROTOCOL_VERSION, NoPluginContract, PluginContract, PluginService, ProviderId,
RegisteredService, ServiceEnvelope, ServiceEnvelopeKind, ServiceError,
ServiceInterfaceDescriptor, ServiceKind, ServiceProtocolVersion, ServiceRequest,
ServiceResponse, decode_service_envelope, decode_service_message, encode_service_envelope,
encode_service_message,
};
pub use stateful_plugin::{
StatefulPlugin, StatefulPluginError, StatefulPluginHandle, StatefulPluginResult,
StatefulPluginSnapshot,
};
pub use typed_dispatch::{
InProcessTypedDispatch, TypedDispatchError, TypedProviderCell, TypedServiceHandle,
TypedServiceKey, TypedServiceRegistry,
};
pub use typed_dispatch_client::{
TypedDispatchClient, TypedDispatchClientError, TypedDispatchClientResult,
TypedServiceClientError, TypedServiceClientResult, TypedServiceEndpoint, invoke_typed_service,
};
pub use version::{ApiVersion, VersionRange};
pub use wire_event_sink::{
NoopWireEventSink, WireEventSink, WireEventSinkError, WireEventSinkHandle,
};
pub use prompt::{
PromptEvent, PromptField, PromptFormField, PromptFormFieldKind, PromptFormSection,
PromptFormValue, PromptOption, PromptPolicy, PromptRequest, PromptResponse, PromptValidation,
PromptValue, PromptWidth,
};
pub use action_dispatch::ActionDispatchRequest;
pub const CURRENT_PLUGIN_API_VERSION: ApiVersion = ApiVersion::new(1, 0);
pub const CURRENT_PLUGIN_ABI_VERSION: ApiVersion = ApiVersion::new(1, 0);
pub const DEFAULT_NATIVE_ENTRY_SYMBOL: &str = "bmux_plugin_entry_v1";
pub fn handle_service<Req, Resp, F>(context: &NativeServiceContext, handler: F) -> ServiceResponse
where
Req: serde::de::DeserializeOwned,
Resp: serde::Serialize,
F: FnOnce(Req, &NativeServiceContext) -> std::result::Result<Resp, ServiceResponse>,
{
let request = match decode_service_message::<Req>(&context.request.payload) {
Ok(req) => req,
Err(error) => {
return ServiceResponse::error("invalid_request", error.to_string());
}
};
match handler(request, context) {
Ok(response) => match encode_service_message(&response) {
Ok(payload) => ServiceResponse::ok(payload),
Err(error) => ServiceResponse::error("response_encode_failed", error.to_string()),
},
Err(error_response) => error_response,
}
}
#[macro_export]
macro_rules! route_service {
($context:ident, { $( $interface:literal, $operation:literal => $handler:expr ),* $(,)? }) => {
match (
$context.request.service.interface_id.as_str(),
$context.request.operation.as_str(),
) {
$(
($interface, $operation) => {
$crate::handle_service(&$context, $handler)
},
)*
(__interface, __operation) => {
$crate::ServiceResponse::error(
"unsupported_service_operation",
format!(
"plugin '{}' does not support service operation '{}:{}'",
$context.plugin_id, __interface, __operation,
),
)
}
}
};
($context:ident, { $( $endpoint:ty => $handler:expr ),* $(,)? }) => {{
let __interface = $context.request.service.interface_id.as_str();
let __operation = $context.request.operation.as_str();
$(
if __interface == <$endpoint as $crate::TypedServiceEndpoint>::INTERFACE_ID.as_str()
&& __operation == <$endpoint as $crate::TypedServiceEndpoint>::OPERATION.as_str()
{
$crate::handle_service::<
<$endpoint as $crate::TypedServiceEndpoint>::Request,
<$endpoint as $crate::TypedServiceEndpoint>::Response,
_,
>(&$context, $handler)
} else
)*
{
$crate::ServiceResponse::error(
"unsupported_service_operation",
format!(
"plugin '{}' does not support service operation '{}:{}'",
$context.plugin_id, __interface, __operation,
),
)
}
}};
}
#[macro_export]
macro_rules! route_command {
($ctx:ident, { $( $name:literal => $handler:expr ),* $(,)? }) => {
match $ctx.command.as_str() {
$( $name => $handler, )*
__unknown => Err($crate::PluginCommandError::unknown_command(__unknown)),
}
};
}
pub mod prelude {
pub use crate::{
ActionDispatchRequest,
EXIT_ERROR,
EXIT_OK,
EXIT_UNAVAILABLE,
EXIT_USAGE,
HostAsyncHandle,
NativeCommandContext,
NativeLifecycleContext,
NativeServiceContext,
PluginCommandError,
PluginEvent,
PromptEvent,
PromptField,
PromptFormField,
PromptFormFieldKind,
PromptFormSection,
PromptFormValue,
PromptOption,
PromptPolicy,
PromptRequest,
PromptResponse,
PromptValidation,
PromptValue,
PromptWidth,
RustPlugin,
ServiceKind,
ServiceResponse,
decode_service_message,
encode_service_message,
handle_service,
};
}
#[doc(hidden)]
pub mod __private {
pub use crate::native_exports::{
activate_export, activate_with_async_bundled, deactivate_export, declared_services_bundled,
handle_event_export, invoke_service_export, manifest_toml_ptr, plugin_instance,
register_typed_services_bundled, run_command_export,
};
}
#[macro_export]
macro_rules! export_plugin {
($plugin_ty:ty, $manifest_toml:expr $(,)?) => {
#[cfg(not(feature = "static-bundled"))]
const _: () = {
fn __bmux_plugin_instance() -> &'static ::std::sync::RwLock<$plugin_ty> {
static INSTANCE: ::std::sync::OnceLock<::std::sync::RwLock<$plugin_ty>> =
::std::sync::OnceLock::new();
$crate::__private::plugin_instance(&INSTANCE)
}
#[unsafe(no_mangle)]
pub extern "C" fn bmux_plugin_entry_v1() -> *const ::std::ffi::c_char {
static MANIFEST: ::std::sync::OnceLock<Option<::std::ffi::CString>> =
::std::sync::OnceLock::new();
$crate::__private::manifest_toml_ptr($manifest_toml, &MANIFEST)
}
#[unsafe(no_mangle)]
pub extern "C" fn bmux_plugin_run_command_with_context_v1(
input_ptr: *const u8,
input_len: usize,
) -> i32 {
$crate::__private::run_command_export(
__bmux_plugin_instance(),
input_ptr,
input_len,
)
}
#[unsafe(no_mangle)]
pub extern "C" fn bmux_plugin_activate_v1(
input_ptr: *const u8,
input_len: usize,
) -> i32 {
$crate::__private::activate_export(__bmux_plugin_instance(), input_ptr, input_len)
}
#[unsafe(no_mangle)]
pub extern "C" fn bmux_plugin_deactivate_v1(
input_ptr: *const u8,
input_len: usize,
) -> i32 {
$crate::__private::deactivate_export(__bmux_plugin_instance(), input_ptr, input_len)
}
#[unsafe(no_mangle)]
pub extern "C" fn bmux_plugin_handle_event_v1(
input_ptr: *const u8,
input_len: usize,
) -> i32 {
$crate::__private::handle_event_export(
__bmux_plugin_instance(),
input_ptr,
input_len,
)
}
#[unsafe(no_mangle)]
pub extern "C" fn bmux_plugin_invoke_service_v1(
input_ptr: *const u8,
input_len: usize,
output_ptr: *mut u8,
output_capacity: usize,
output_len: *mut usize,
) -> i32 {
$crate::__private::invoke_service_export(
__bmux_plugin_instance(),
input_ptr,
input_len,
output_ptr,
output_capacity,
output_len,
)
}
};
};
}
#[macro_export]
macro_rules! bundled_plugin_vtable {
($plugin_ty:ty, $manifest_toml:expr $(,)?) => {{
fn __instance() -> &'static ::std::sync::RwLock<$plugin_ty> {
static INSTANCE: ::std::sync::OnceLock<::std::sync::RwLock<$plugin_ty>> =
::std::sync::OnceLock::new();
$crate::__private::plugin_instance(&INSTANCE)
}
fn __entry() -> *const ::std::ffi::c_char {
static MANIFEST: ::std::sync::OnceLock<Option<::std::ffi::CString>> =
::std::sync::OnceLock::new();
$crate::__private::manifest_toml_ptr($manifest_toml, &MANIFEST)
}
fn __run_command_with_context(input_ptr: *const u8, input_len: usize) -> i32 {
$crate::__private::run_command_export(__instance(), input_ptr, input_len)
}
fn __activate(input_ptr: *const u8, input_len: usize) -> i32 {
$crate::__private::activate_export(__instance(), input_ptr, input_len)
}
fn __activate_with_async(
context: $crate::NativeLifecycleContext,
async_handle: $crate::HostAsyncHandle,
) -> i32 {
$crate::__private::activate_with_async_bundled(__instance(), context, async_handle)
}
fn __deactivate(input_ptr: *const u8, input_len: usize) -> i32 {
$crate::__private::deactivate_export(__instance(), input_ptr, input_len)
}
fn __handle_event(input_ptr: *const u8, input_len: usize) -> i32 {
$crate::__private::handle_event_export(__instance(), input_ptr, input_len)
}
fn __invoke_service(
input_ptr: *const u8,
input_len: usize,
output_ptr: *mut u8,
output_capacity: usize,
output_len: *mut usize,
) -> i32 {
$crate::__private::invoke_service_export(
__instance(),
input_ptr,
input_len,
output_ptr,
output_capacity,
output_len,
)
}
fn __register_typed_services(
context: $crate::TypedServiceRegistrationContext<'_>,
) -> $crate::TypedServiceRegistry {
$crate::__private::register_typed_services_bundled(__instance(), context)
}
fn __declared_services() -> $crate::Result<Vec<$crate::PluginService>> {
$crate::__private::declared_services_bundled::<$plugin_ty>()
}
$crate::StaticPluginVtable {
entry: __entry,
run_command_with_context: __run_command_with_context,
activate: __activate,
activate_with_async: __activate_with_async,
deactivate: __deactivate,
handle_event: __handle_event,
invoke_service: __invoke_service,
register_typed_services: __register_typed_services,
declared_services: __declared_services,
}
}};
}
#[derive(Clone, Copy)]
pub struct StaticPluginVtable {
pub entry: fn() -> *const std::ffi::c_char,
pub run_command_with_context: fn(*const u8, usize) -> i32,
pub activate: fn(*const u8, usize) -> i32,
pub activate_with_async: fn(NativeLifecycleContext, HostAsyncHandle) -> i32,
pub deactivate: fn(*const u8, usize) -> i32,
pub handle_event: fn(*const u8, usize) -> i32,
pub invoke_service: fn(*const u8, usize, *mut u8, usize, *mut usize) -> i32,
pub register_typed_services:
fn(context: TypedServiceRegistrationContext<'_>) -> TypedServiceRegistry,
pub declared_services: fn() -> Result<Vec<PluginService>>,
}
impl std::fmt::Debug for StaticPluginVtable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StaticPluginVtable").finish_non_exhaustive()
}
}