#![cfg_attr(feature = "fail-on-warnings", deny(warnings))]
#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
#![allow(clippy::multiple_crate_versions)]
use bcode_plugin_sdk::prelude::*;
#[derive(Default)]
pub struct HelloPlugin {
event_count: usize,
}
fn render_shell_visual(context: &NativeServiceContext) -> Option<ServiceResponse> {
if context.request.interface_id != bcode_plugin_sdk::tui_visual::TUI_VISUAL_ADAPTER_INTERFACE_ID
|| context.request.operation != bcode_plugin_sdk::tui_visual::OP_RENDER_TUI_VISUAL
{
return None;
}
let request = match context
.request
.payload_json::<bcode_plugin_sdk::tui_visual::RenderTuiVisualRequest>()
{
Ok(request) => request,
Err(error) => {
return Some(ServiceResponse::error(
"invalid_visual_request",
error.to_string(),
));
}
};
Some(
ServiceResponse::json(&bcode_plugin_sdk::tui_visual::RenderTuiVisualResponse {
version: bcode_plugin_sdk::tui_visual::TUI_VISUAL_ADAPTER_CONTRACT_VERSION,
render_mode: "transcript_block".to_owned(),
anchors: Vec::new(),
title: Some("Hello user shell".to_owned()),
timeout_ms: None,
rows: vec![bcode_plugin_sdk::tui_visual::SerializedTuiRow {
spans: vec![bcode_plugin_sdk::tui_visual::SerializedTuiSpan {
text: format!("hello dynamic {}", request.invocation_id),
role: Some(bcode_plugin_sdk::tui_visual::SerializedTuiStyleRole::Accent),
foreground: bcode_plugin_sdk::tui_visual::SerializedTuiColor::LightCyan,
modifiers: vec![bcode_plugin_sdk::tui_visual::SerializedTuiModifier::Bold],
}],
}],
})
.unwrap_or_else(|error| {
ServiceResponse::error("visual_response_encode_failed", error.to_string())
}),
)
}
impl RustPlugin for HelloPlugin {
fn activate(&mut self) -> Result<(), PluginError> {
Ok(())
}
fn register_auth_providers(&mut self, registrar: AuthRegistrar) -> Result<(), PluginError> {
registrar
.register(&hello_auth_provider())
.map_err(|error| PluginError::failed(error.to_string()))
}
fn deactivate(&mut self) -> Result<(), PluginError> {
Ok(())
}
fn invoke_service(&mut self, context: NativeServiceContext) -> ServiceResponse {
if let Some(response) = render_shell_visual(&context) {
return response;
}
if context.request.interface_id == bcode_tool::TOOL_SERVICE_INTERFACE_ID
&& context.request.operation == bcode_tool::OP_PREPARE_TOOL
{
return prepare_tool_service_response(
&context.request,
[hello_bridge_definition()],
|_request, _definition| Ok(bcode_plugin_sdk::ToolPolicyPreparation::read_only()),
);
}
if context.request.interface_id == bcode_tool::TOOL_SERVICE_INTERFACE_ID
&& context.request.operation == bcode_tool::OP_INVOKE_TOOL
{
let request = match context
.request
.payload_json::<bcode_tool::ToolInvocationRequest>()
{
Ok(request) => request,
Err(error) => {
return ServiceResponse::error("invalid_tool_request", error.to_string());
}
};
if request.name != "hello_bridge" {
return ServiceResponse::error("unsupported_tool", request.name);
}
return bridge_tool_response(&context, &request.tool_call_id);
}
if context.request.interface_id != "example-hello/v1" {
return ServiceResponse::error(
"unsupported_interface",
"unsupported hello service interface",
);
}
match context.request.operation.as_str() {
"echo" => ServiceResponse::ok(context.request.payload),
"bridge-exchange" => {
let request = ServiceBridgeRequest::Exchange(bcode_tool::ToolExchangeRequest {
invocation_id: "hello-invocation".to_string(),
exchange_id: "hello-exchange".to_string(),
producer_id: "example.hello".to_string(),
schema: "example.hello.exchange".to_string(),
schema_version: 1,
payload: serde_json::Value::Null,
response_policy: bcode_tool::ToolExchangeResponsePolicy::Required,
});
match context.bridge.request(&request) {
Ok(response) => ServiceResponse::json(&response).unwrap_or_else(|error| {
ServiceResponse::error("bridge_response_encode_failed", error.to_string())
}),
Err(error) => ServiceResponse::error("bridge_failed", error.to_string()),
}
}
"emit-event" => {
context.events.emit(b"hello-event");
ServiceResponse::text("event-emitted")
}
"bridge-all" => bridge_responses(&context, "hello-invocation"),
"wait-cancelled" => {
if context
.cancellation
.wait_cancelled(std::time::Duration::from_secs(5))
{
ServiceResponse::text("cancelled")
} else {
ServiceResponse::text("timeout")
}
}
"abort-process" => std::process::abort(),
"event-count" => ServiceResponse::text(self.event_count.to_string()),
_ => ServiceResponse::error(
"unsupported_operation",
"unsupported hello service operation",
),
}
}
fn handle_event(&mut self, context: NativeEventContext) -> Result<(), PluginError> {
if context.event.topic == "example.event" || context.event.topic == "bcode.session.event" {
self.event_count += 1;
}
Ok(())
}
}
fn bridge_requests(invocation_id: &str) -> [ServiceBridgeRequest; 4] {
[
ServiceBridgeRequest::Exchange(bcode_tool::ToolExchangeRequest {
invocation_id: invocation_id.to_string(),
exchange_id: "hello-exchange".to_string(),
producer_id: "example.hello".to_string(),
schema: "example.hello.exchange".to_string(),
schema_version: 1,
payload: serde_json::Value::Null,
response_policy: bcode_tool::ToolExchangeResponsePolicy::Required,
}),
ServiceBridgeRequest::ReceiveInput {
invocation_id: invocation_id.to_string(),
timeout_ms: None,
},
ServiceBridgeRequest::InvokeService(bcode_tool::ToolInvocationServiceRequest {
invocation_id: invocation_id.to_string(),
request_id: "hello-service".to_string(),
route_id: None,
interface_id: "example.nested/v1".to_string(),
operation: "run".to_string(),
payload: serde_json::Value::Null,
}),
ServiceBridgeRequest::WriteArtifact(bcode_tool::ToolArtifactWriteRequest {
invocation_id: invocation_id.to_string(),
artifact_id: "hello-artifact".to_string(),
content_type: "text/plain".to_string(),
bytes: b"hello".to_vec(),
metadata: serde_json::Value::Null,
}),
]
}
fn bridge_responses(context: &NativeServiceContext, invocation_id: &str) -> ServiceResponse {
let responses = bridge_requests(invocation_id)
.iter()
.map(|request| context.bridge.request(request))
.collect::<Result<Vec<_>, _>>();
match responses {
Ok(responses) => ServiceResponse::json(&responses).unwrap_or_else(|error| {
ServiceResponse::error("bridge_response_encode_failed", error.to_string())
}),
Err(error) => ServiceResponse::error("bridge_failed", error.to_string()),
}
}
fn hello_bridge_definition() -> bcode_tool::ToolDefinition {
bcode_tool::ToolDefinition {
name: "hello_bridge".to_string(),
description: "Exercise every generic invocation capability".to_string(),
input_schema: serde_json::json!({"type": "object"}),
}
}
fn bridge_tool_response(context: &NativeServiceContext, invocation_id: &str) -> ServiceResponse {
let response = bridge_responses(context, invocation_id);
if let Some(error) = response.error {
return ServiceResponse {
payload: response.payload,
error: Some(error),
};
}
ServiceResponse::json(&bcode_tool::ToolInvocationResponse {
output: invocation_id.to_string(),
is_error: false,
content: Vec::new(),
full_output: None,
result: None,
})
.unwrap_or_else(|error| {
ServiceResponse::error("tool_response_encode_failed", error.to_string())
})
}
fn hello_auth_provider() -> bcode_provider_auth_models::AuthProviderContribution {
bcode_provider_auth_models::AuthProviderContribution {
schema_version: bcode_provider_auth_models::AUTH_PROVIDER_CONTRIBUTION_SCHEMA_VERSION,
provider_id: "example-hello".to_owned(),
display_name: "Example Hello".to_owned(),
methods: vec![
bcode_provider_auth_models::AuthMethodContribution::SecretFields {
method_id: "api_key".to_owned(),
display_name: "API key".to_owned(),
fields: vec![bcode_provider_auth_models::AuthSecretField {
invocation_env: Vec::new(),
discovery_sources: Vec::new(),
credential_id: "api_key".to_owned(),
storage_key: "EXAMPLE_HELLO_API_KEY".to_owned(),
prompt: "Example API key".to_owned(),
optional: false,
validation: bcode_provider_auth_models::AuthSecretValidation::default(),
}],
supports_verification: false,
supports_revocation: false,
},
],
}
}
#[must_use]
pub fn static_plugin() -> StaticPluginVtable {
bcode_plugin_sdk::static_plugin_vtable!(HelloPlugin, include_str!("../bcode-plugin.toml"))
}
bcode_plugin_sdk::export_plugin!(HelloPlugin, include_str!("../bcode-plugin.toml"));