Skip to main content
Glama
management.rs15.8 kB
use std::collections::{ BTreeMap, HashSet, }; use itertools::Itertools; use serde::{ Deserialize, Serialize, }; use si_id::SchemaId; use telemetry::prelude::*; use super::{ EventualParent, FuncBinding, FuncBindingResult, }; use crate::{ DalContext, EdgeWeightKind, Func, FuncId, Prop, Schema, SchemaVariant, SchemaVariantId, cached_module::CachedModule, func::binding::FuncBindingError, management::prototype::{ ManagementPrototype, ManagementPrototypeId, ManagementPrototypeParent, }, }; #[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash)] #[serde(rename_all = "camelCase")] pub struct ManagementBinding { pub schema_ids: Option<Vec<SchemaId>>, pub schema_variant_id: Option<SchemaVariantId>, pub management_prototype_id: ManagementPrototypeId, pub func_id: FuncId, } const INCOMING_CONNECTION_TYPE: &str = "{ component: string, socket: string; value: any }"; const DEFAULT_THIS_COMPONENT_IFACE: &str = "object"; const DEFAULT_THIS_INCOMING_CONNECTIONS: &str = "object"; const DEFAULT_COMPONENT_TYPES: &str = "object"; impl ManagementBinding { #[instrument( level = "info", skip(ctx), name = "func.binding.action.create_management_binding" )] pub async fn create_management_binding( ctx: &DalContext, func_id: FuncId, schema_ids: Option<Vec<SchemaId>>, schema_variant_id: Option<SchemaVariantId>, ) -> FuncBindingResult<Vec<FuncBinding>> { let func = Func::get_by_id(ctx, func_id).await?; let mut prototype = None; // don't add binding if schema_variant_id is locked if let Some(schema_variant_id) = schema_variant_id { SchemaVariant::error_if_locked(ctx, schema_variant_id).await?; prototype = Some( ManagementPrototype::new( ctx, func.name.to_owned(), func.description.to_owned(), func.id, ManagementPrototypeParent::SchemaVariant(schema_variant_id), ) .await?, ); } if let Some(schema_ids) = schema_ids { match &prototype { Some(prototype) => { for schema_id in schema_ids { Schema::add_edge_to_management_prototype( ctx, schema_id, prototype.id(), EdgeWeightKind::ManagementPrototype, ) .await?; } } None => { prototype = Some( ManagementPrototype::new( ctx, func.name.to_owned(), func.description.to_owned(), func.id, ManagementPrototypeParent::Schemas(schema_ids), ) .await?, ); } } // this should always be some here if let Some(prototype) = &prototype { ManagementPrototype::promote_to_overlay(ctx, prototype.id()).await?; } } FuncBinding::for_func_id(ctx, func_id).await } pub async fn compile_management_types( ctx: &DalContext, func_id: FuncId, ) -> FuncBindingResult<String> { let default_types = ( DEFAULT_THIS_COMPONENT_IFACE.to_owned(), DEFAULT_THIS_INCOMING_CONNECTIONS.to_owned(), DEFAULT_COMPONENT_TYPES.to_owned(), DEFAULT_COMPONENT_TYPES.to_owned(), ); let mut default_variant_root_props = BTreeMap::new(); let mut ts_type_cache = BTreeMap::new(); let ( this_component_iface, this_incoming_connections, component_create_type, component_input_type, ) = match ManagementPrototype::prototype_id_for_func_id(ctx, func_id).await? { Some(prototype_id) => { let mut installable_schemas: HashSet<String> = CachedModule::latest_modules(ctx) .await? .into_iter() .map(|m| m.schema_name) .collect(); let mut component_create_types = vec![]; let mut component_input_types = vec![]; for schema in Schema::list(ctx).await? { // Since it's already installed, we don't include it in "installable_schemas" installable_schemas.remove(schema.name()); let variant_id = Schema::default_variant_id(ctx, schema.id()).await?; let root_prop_id = SchemaVariant::get_root_prop_id(ctx, variant_id).await?; default_variant_root_props.insert(schema.id(), root_prop_id); let sv_type = Prop::ts_type(ctx, root_prop_id).await?; let json_name = serde_json::to_string(schema.name())?; let component_input_type = format!( r#" {{ kind: {json_name}, properties?: {sv_type}, geometry?: {{ [key: string]: Geometry }}, incomingConnections?: {{ [socket: string]: {INCOMING_CONNECTION_TYPE} | {INCOMING_CONNECTION_TYPE}[] }}, connect?: {{ from: string, to: {{ component: string; socket: string; }} }}[], parent?: string, }} "# ); let component_create_type = format!( r#" {{ kind: {json_name}, properties?: {sv_type}, attributes?: {{ [path: string]: ValueOrSource }}, geometry?: Geometry | {{ [key: string]: Geometry }}, connect?: Connection[], parent?: string, }} "# ); ts_type_cache.insert(root_prop_id, sv_type); component_create_types.push(component_create_type); component_input_types.push(component_input_type); } // Add a generic type for any uninstalled modules, but with the names so that // you know what can be installed if !installable_schemas.is_empty() { let kinds: Vec<String> = installable_schemas .into_iter() .sorted() .map(|name| serde_json::to_string(&name)) .try_collect()?; let kinds = kinds.join(" | "); component_input_types.push(format!( r#" {{ kind: {kinds}, properties?: {{ [key: string]: unknown }}, geometry?: {{ [key: string]: Geometry }}, connect?: {{ from: string, to: {{ component: string; socket: string; }} }}[], parent?: string, }} "# )); component_create_types.push(format!( r#" {{ kind: {kinds}, properties?: {{ [key: string]: any }}, attributes?: {{ [path: string]: ValueOrSource }}, geometry?: Geometry | {{ [key: string]: Geometry }}, connect?: Connection[], parent?: string, }} "# )) } let component_create_type = component_create_types.join("|\n"); let component_input_type = component_input_types.join("|\n"); let mut this_component_iface = String::new(); let mut root_prop_ids = vec![]; if let Some(schema_ids) = ManagementPrototype::schema_ids(ctx, prototype_id).await? { for schema_id in schema_ids { let root_prop_id = match default_variant_root_props.get(&schema_id) { Some(root_prop_id) => *root_prop_id, None => { let variant_id = Schema::default_variant_id(ctx, schema_id).await?; SchemaVariant::get_root_prop_id(ctx, variant_id).await? } }; root_prop_ids.push(root_prop_id); } } if let Some(schema_variant_id) = ManagementPrototype::schema_variant_id(ctx, prototype_id).await? { let root_prop_id = SchemaVariant::get_root_prop_id(ctx, schema_variant_id).await?; root_prop_ids.push(root_prop_id); } for root_prop_id in root_prop_ids { let ts_type = match ts_type_cache.get(&root_prop_id) { Some(ts_type) => ts_type.clone(), None => Prop::ts_type(ctx, root_prop_id).await?, }; if !this_component_iface.is_empty() { this_component_iface.push_str(" | "); } this_component_iface.push_str(&ts_type); } ( this_component_iface, DEFAULT_THIS_INCOMING_CONNECTIONS.into(), component_create_type, component_input_type, ) } None => default_types, }; Ok(format!( r#" type ValueSource = | {{ component: string; path: string; func?: string; }} | {{ value?: JsonValue }} | null; type JsonValue = | string | number | boolean | null | {{ string: JsonValue }} | JsonValue[]; type ValueOrSource = JsonValue | {{ $source: ValueSource }}; type Geometry = {{ x?: number, y?: number, width?: number, height?: number, }}; type Connection = | {{ from: string, to: {{ component: string, socket: string, }}, }} | {{ from: {{ component: string, socket: string, }}, to: string, }}; type Output = {{ status: 'ok' | 'error'; ops?: {{ views?: {{ create?: string[]; remove?: string[] }}, create?: {{ [name: string]: {component_create_type} }}, update?: {{ [name: string]: {{ properties?: {{ [name: string]: unknown }}, attributes?: {{ [path: string]: ValueOrSource }}, geometry?: {{ [view: string]: Geometry }}, connect?: {{ add?: Connection[], remove?: Connection[], }}, parent?: string, }} }}, delete?: string[], erase?: string[], remove?: {{ [key: string]: string[] }}, actions?: {{ [key: string]: {{ add?: ("create" | "update" | "refresh" | "delete" | string)[]; remove?: ("create" | "update" | "refresh" | "delete" | string)[]; }} }} }}, message?: string | null; }}; type Input = {{ currentView: string, thisComponent: {{ properties: {this_component_iface}, sources: {{ [path: string]: {{ component: string; path: string; func?: string; }} }}, geometry: {{ [view: string]: Geometry }}, incomingConnections: {this_incoming_connections}, }}, components: {{ [name: string]: {component_input_type} }}, variantSocketMap: Record<string, number>, }};"# )) } pub async fn assemble_management_bindings( ctx: &DalContext, func_id: FuncId, ) -> FuncBindingResult<Vec<FuncBinding>> { let mut bindings = vec![]; for management_prototype_id in ManagementPrototype::list_ids_for_func_id(ctx, func_id).await? { let schema_variant_id = match ManagementPrototype::schema_variant_id(ctx, management_prototype_id).await { Ok(sv_id) => sv_id, Err(err) => { error!(error=?err, "Could not get bindings for func_id {func_id}"); continue; } }; let schema_ids = match ManagementPrototype::schema_ids(ctx, management_prototype_id).await { Ok(s_ids) => s_ids, Err(err) => { error!(error=?err, "Could not get bindings for func_id {func_id}"); continue; } }; bindings.push(FuncBinding::Management(ManagementBinding { schema_ids, schema_variant_id, func_id, management_prototype_id, })); } Ok(bindings) } pub async fn port_binding_to_new_func( &self, ctx: &DalContext, new_func_id: FuncId, ) -> FuncBindingResult<Vec<FuncBinding>> { ManagementPrototype::remove(ctx, self.management_prototype_id).await?; Self::create_management_binding( ctx, new_func_id, self.schema_ids.clone(), self.schema_variant_id, ) .await?; FuncBinding::for_func_id(ctx, new_func_id).await } #[instrument( level = "info", skip(ctx), name = "func.binding.action.delete_management_binding" )] pub async fn delete_management_binding( ctx: &DalContext, management_prototype_id: ManagementPrototypeId, ) -> FuncBindingResult<EventualParent> { let eventual_parent: EventualParent; // don't delete binding if parent is locked if let Some(schema_variant_id) = ManagementPrototype::schema_variant_id(ctx, management_prototype_id).await? { SchemaVariant::error_if_locked(ctx, schema_variant_id).await?; eventual_parent = EventualParent::SchemaVariant(schema_variant_id); } else if let Some(schema_ids) = ManagementPrototype::schema_ids(ctx, management_prototype_id).await? { eventual_parent = EventualParent::Schemas(schema_ids); } else { return Err(FuncBindingError::ManagementPrototypeNoParent( management_prototype_id, )); } ManagementPrototype::remove(ctx, management_prototype_id).await?; Ok(eventual_parent) } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server