Skip to main content
Glama
save.rs20.5 kB
use std::collections::HashSet; use telemetry::prelude::*; use crate::{ AttributePrototype, AttributePrototypeId, AttributeValue, Component, ComponentId, DalContext, EdgeWeightKind, Func, FuncBackendResponseType, FuncId, OutputSocket, Prop, SchemaVariant, SchemaVariantId, WorkspaceSnapshotError, action::prototype::{ ActionKind, ActionPrototype, }, attribute::prototype::argument::{ AttributePrototypeArgument, AttributePrototypeArgumentId, }, func::{ AttributePrototypeArgumentBag, AttributePrototypeBag, FuncKind, argument::{ FuncArgument, FuncArgumentError, }, associations::FuncAssociations, authoring::{ FuncAuthoringError, FuncAuthoringResult, }, intrinsics::IntrinsicFunc, }, schema::variant::leaves::{ LeafInputLocation, LeafKind, }, workspace_snapshot::graph::WorkspaceSnapshotGraphError, }; #[instrument( name = "func.authoring.save_func.update_associations", level = "debug", skip(ctx) )] pub(crate) async fn update_associations( ctx: &DalContext, func: &Func, associations: FuncAssociations, ) -> FuncAuthoringResult<()> { match func.kind { FuncKind::Action => match associations { FuncAssociations::Action { kind, schema_variant_ids, } => update_action_associations(ctx, func, kind, schema_variant_ids).await, invalid => { return Err(FuncAuthoringError::InvalidFuncAssociationsForFunc( invalid, func.id, func.kind, )); } }, // NOTE(nick): I'm re-reading the below comment and thinking we need to just destroy // associations for attribute funcs. If they are not useful, demolish them. // ------------------------------------------------------------------------------ // don't update attribute associations this way // attribute associations are updated through calling // create/remove/update attribute binding directly FuncKind::Attribute => Ok(()), FuncKind::Authentication => match associations { FuncAssociations::Authentication { schema_variant_ids } => { update_authentication_associations(ctx, func, schema_variant_ids).await } invalid => { return Err(FuncAuthoringError::InvalidFuncAssociationsForFunc( invalid, func.id, func.kind, )); } }, FuncKind::CodeGeneration => match associations { FuncAssociations::CodeGeneration { schema_variant_ids, component_ids, inputs, } => { update_leaf_associations( ctx, func, schema_variant_ids, component_ids, &inputs, LeafKind::CodeGeneration, ) .await } invalid => Err(FuncAuthoringError::InvalidFuncAssociationsForFunc( invalid, func.id, func.kind, )), }, FuncKind::Qualification => match associations { FuncAssociations::Qualification { schema_variant_ids, component_ids, inputs, } => { update_leaf_associations( ctx, func, schema_variant_ids, component_ids, &inputs, LeafKind::Qualification, ) .await } invalid => Err(FuncAuthoringError::InvalidFuncAssociationsForFunc( invalid, func.id, func.kind, )), }, kind => Err(FuncAuthoringError::FuncCannotHaveAssociations( func.id, kind, associations, )), } } #[instrument( name = "func.authoring.save_func.update_associations.action", level = "debug", skip(ctx) )] async fn update_action_associations( ctx: &DalContext, func: &Func, kind: ActionKind, schema_variant_ids: Vec<SchemaVariantId>, ) -> FuncAuthoringResult<()> { let id_set: HashSet<SchemaVariantId> = HashSet::from_iter(schema_variant_ids.iter().copied()); // Add the new action to schema variants who do not already have a prototype or re-create the // prototype if the kind has been mutated. for schema_variant_id in schema_variant_ids { let existing_action_prototypes = ActionPrototype::for_variant(ctx, schema_variant_id).await?; // Assume that the prototype needs to be created. Bail the moment that we know one already // exists the moment that we know that the kind has been mutated, which means we will need // to re-create the prototype with the new kind. let mut needs_creation = true; let mut outdated_action_prototype_id = None; for (existing_action_prototype_id, exiting_action_prototype_kind) in existing_action_prototypes.iter().map(|p| (p.id, p.kind)) { let prototype_func_id = ActionPrototype::func_id(ctx, existing_action_prototype_id).await?; // Match found! We need to now decide if we need to re-create the prototype. If the user // is keeping the prototype kind the same, then we don't need to create a new prototype. // If the user wishes to mutate the kind, then we need to delete the existing prototype // and create a new one. if func.id == prototype_func_id { if kind == exiting_action_prototype_kind { needs_creation = false; } else { outdated_action_prototype_id = Some(existing_action_prototype_id); } break; } } // Any time that we need to create a new prototype, we need to first check that it will not // collide with an existing prototype using the same, non-manual kind that we provided. if needs_creation { if kind != ActionKind::Manual && existing_action_prototypes.iter().any(|p| p.kind == kind) { return Err(FuncAuthoringError::ActionKindAlreadyExists( kind, schema_variant_id, )); } // Remove the prototype that needs to be re-created, if necessary. if let Some(outdated_action_prototype_id) = outdated_action_prototype_id { ActionPrototype::remove(ctx, outdated_action_prototype_id).await?; } ActionPrototype::new( ctx, kind, func.name.to_owned(), func.description.to_owned(), schema_variant_id, func.id, ) .await?; } } // Remove action prototypes using our func from schema variants that weren't seen. for action_prototype_id in ActionPrototype::list_for_func_id(ctx, func.id).await? { let schema_variant_id = ActionPrototype::schema_variant_id(ctx, action_prototype_id).await?; if !id_set.contains(&schema_variant_id) { ActionPrototype::remove(ctx, action_prototype_id).await?; } } Ok(()) } #[instrument( name = "func.authoring.save_func.update_associations.authentication", level = "debug", skip(ctx) )] async fn update_authentication_associations( ctx: &DalContext, func: &Func, schema_variant_ids: Vec<SchemaVariantId>, ) -> FuncAuthoringResult<()> { let mut id_set = HashSet::new(); // Add the new authentication prototype to schema variants who do not already have a prototype. // We do not need to re-create or edit the prototypes that already exist because the prototype // is merely an edge. for schema_variant_id in schema_variant_ids { let existing_auth_func_ids = SchemaVariant::list_auth_func_ids_for_id(ctx, schema_variant_id).await?; if !existing_auth_func_ids.iter().any(|id| *id == func.id) { SchemaVariant::new_authentication_prototype(ctx, func.id, schema_variant_id).await?; } id_set.insert(schema_variant_id); } // Remove authentication prototypes from schema variants that haven't been seen. for schema_variant_id in SchemaVariant::list_schema_variant_ids_using_auth_func_id(ctx, func.id).await? { if !id_set.contains(&schema_variant_id) { SchemaVariant::remove_authentication_prototype(ctx, func.id, schema_variant_id).await?; } } Ok(()) } #[instrument( name = "func.authoring.save_func.update_associations.leaf", level = "info", skip(ctx) )] async fn update_leaf_associations( ctx: &DalContext, func: &Func, schema_variant_ids: Vec<SchemaVariantId>, component_ids: Vec<ComponentId>, inputs: &[LeafInputLocation], leaf_kind: LeafKind, ) -> FuncAuthoringResult<()> { let mut id_set = HashSet::new(); // Populate the id set with the provided schema variant ids as well as the schema variant ids // for the provided components. id_set.extend(schema_variant_ids); for component_id in component_ids { // TODO(nick): destroy nilId. Log a warning at the moment in case the frontend sends value // for no-ops. I will come back and destroy nil id soon. if component_id == ComponentId::NONE { warn!("skipping component id set to nil id"); } else { id_set.insert(Component::schema_variant_id(ctx, component_id).await?); } } let mut views = Vec::new(); for schema_variant_id in id_set { let attribute_prototype_id = SchemaVariant::upsert_leaf_function(ctx, schema_variant_id, leaf_kind, inputs, func) .await?; views.push(AttributePrototypeBag::assemble(ctx, attribute_prototype_id).await?); } let key = Some(func.name.to_owned()); save_attr_func_prototypes(ctx, func, views, false, key).await?; Ok(()) } #[instrument( name = "func.authoring.save_func.save_attr_func_prototypes", level = "info", skip(ctx) )] async fn save_attr_func_prototypes( ctx: &DalContext, func: &Func, prototype_bags: Vec<AttributePrototypeBag>, attempt_to_use_default_prototype: bool, key: Option<String>, ) -> FuncAuthoringResult<FuncBackendResponseType> { let mut id_set = HashSet::new(); let mut computed_backend_response_type = func.backend_response_type; // Update all prototypes using the func. for prototype_bag in prototype_bags { // TODO(nick): don't use the nil id in the future. let attribute_prototype_id = if AttributePrototypeId::NONE == prototype_bag.id { create_new_attribute_prototype(ctx, &prototype_bag, func.id, key.clone()).await? } else { AttributePrototype::update_func_by_id(ctx, prototype_bag.id, func.id).await?; prototype_bag.id }; id_set.insert(attribute_prototype_id); // Use the attribute prototype id variable rather than the one off the iterator so that we // don't use the nil one by accident. save_attr_func_proto_arguments( ctx, attribute_prototype_id, prototype_bag.prototype_arguments, ) .await?; } // Reset all prototypes that are unused. for attribute_prototype_id in AttributePrototype::list_ids_for_func_id(ctx, func.id).await? { if !id_set.contains(&attribute_prototype_id) { reset_attribute_prototype( ctx, attribute_prototype_id, attempt_to_use_default_prototype, ) .await?; } } // Use the "unset" response type if all bindings have been removed. if id_set.is_empty() { computed_backend_response_type = FuncBackendResponseType::Unset; } Ok(computed_backend_response_type) } #[instrument( name = "func.authoring.save_func.delete_attribute_prototype_and_args", level = "info", skip(ctx) )] pub(crate) async fn delete_attribute_prototype_and_args( ctx: &DalContext, attribute_prototype_id: AttributePrototypeId, ) -> FuncAuthoringResult<()> { let current_attribute_prototype_arguments = AttributePrototypeArgument::list_ids_for_prototype(ctx, attribute_prototype_id).await?; for apa in current_attribute_prototype_arguments { AttributePrototypeArgument::remove(ctx, apa).await?; } AttributePrototype::remove(ctx, attribute_prototype_id).await?; Ok(()) } // NOTE(nick,john): this is doing way too much bullshit. We probably need to break out its usages // and users. #[instrument( name = "func.authoring.save_func.reset_attribute_prototype", level = "info", skip(ctx) )] pub(crate) async fn reset_attribute_prototype( ctx: &DalContext, attribute_prototype_id: AttributePrototypeId, attempt_to_use_default_prototype: bool, ) -> FuncAuthoringResult<()> { if attempt_to_use_default_prototype { if let Some(attribute_value_id) = AttributePrototype::attribute_value_id(ctx, attribute_prototype_id).await? { AttributeValue::use_default_prototype(ctx, attribute_value_id).await?; return Ok(()); } } // If we aren't trying to use the default prototype, or the default prototype is the same as the // prototype we're trying to 'reset', then set this prototype to be identity and remove all existing arguments. // By setting to identity, this ensures that IF the user regenerates the schema variant def in the future, // we'll correctly reset the value sources based on what's in that code let identity_func_id = Func::find_intrinsic(ctx, IntrinsicFunc::Identity).await?; AttributePrototype::update_func_by_id(ctx, attribute_prototype_id, identity_func_id).await?; // loop through and delete all existing attribute prototype arguments let current_attribute_prototype_arguments = AttributePrototypeArgument::list_ids_for_prototype(ctx, attribute_prototype_id).await?; for apa in current_attribute_prototype_arguments { AttributePrototypeArgument::remove(ctx, apa).await?; } Ok(()) } #[instrument( name = "func.authoring.save_func.save_attr_func_proto_arguments", level = "info", skip(ctx) )] pub(crate) async fn save_attr_func_proto_arguments( ctx: &DalContext, attribute_prototype_id: AttributePrototypeId, arguments: Vec<AttributePrototypeArgumentBag>, ) -> FuncAuthoringResult<()> { let mut id_set = HashSet::new(); for arg in &arguments { // Ensure the func argument exists before continuing. By continuing, we will not add the // attribute prototype to the id set and will be deleted. if let Err(err) = FuncArgument::get_by_id(ctx, arg.func_argument_id).await { match err { FuncArgumentError::WorkspaceSnapshot( WorkspaceSnapshotError::WorkspaceSnapshotGraph( WorkspaceSnapshotGraphError::NodeWithIdNotFound(raw_id), ), ) if raw_id == arg.func_argument_id.into() => continue, err => return Err(err.into()), } } // Always remove and recreate the argument because the func argument or input socket // could have changed. if AttributePrototypeArgumentId::NONE != arg.id { AttributePrototypeArgument::remove_or_no_op(ctx, arg.id).await?; } let value_source: ValueSource = if let Some(input_socket_id) = arg.input_socket_id { input_socket_id.into() } else if let Some(prop_id) = arg.prop_id { prop_id.into() } else { return Err(FuncAuthoringError::NoInputLocationGiven( attribute_prototype_id, arg.func_argument_id, )); }; let attribute_prototype_argument = AttributePrototypeArgument::new( ctx, attribute_prototype_id, arg.func_argument_id, value_source, ) .await?; id_set.insert(attribute_prototype_argument.id); } for attribute_prototype_argument_id in AttributePrototypeArgument::list_ids_for_prototype(ctx, attribute_prototype_id).await? { if !id_set.contains(&attribute_prototype_argument_id) { AttributePrototypeArgument::remove_or_no_op(ctx, attribute_prototype_argument_id) .await?; } } Ok(()) } /// Creates a new attribute prototype for a given prop/socket/component id. /// Also removes the existing prototype and args that exist so we don't end up /// with duplicates pub(crate) async fn create_new_attribute_prototype( ctx: &DalContext, prototype_bag: &AttributePrototypeBag, func_id: FuncId, key: Option<String>, ) -> FuncAuthoringResult<AttributePrototypeId> { let attribute_prototype = AttributePrototype::new(ctx, func_id).await?; // TODO(nick): just destroy and burn nilId to the ground. let component_id_cannot_be_nil_id = match prototype_bag.component_id { None | Some(ComponentId::NONE) => None, Some(component_id) => Some(component_id), }; let mut affected_attribute_value_ids = Vec::new(); if let Some(prop_id) = prototype_bag.prop_id { if let Some(component_id) = component_id_cannot_be_nil_id { let attribute_value_ids = Component::attribute_values_for_prop_id(ctx, component_id, prop_id).await?; for attribute_value_id in attribute_value_ids { AttributeValue::set_component_prototype_id( ctx, attribute_value_id, attribute_prototype.id, None, ) .await?; affected_attribute_value_ids.push(attribute_value_id); } } else { // remove the existing attribute prototype and arguments if let Some(existing_proto) = AttributePrototype::find_for_prop(ctx, prop_id, &key).await? { delete_attribute_prototype_and_args(ctx, existing_proto).await?; } Prop::add_edge_to_attribute_prototype( ctx, prop_id, attribute_prototype.id, EdgeWeightKind::Prototype(key), ) .await?; } } else if let Some(output_socket_id) = prototype_bag.output_socket_id { if let Some(component_id) = component_id_cannot_be_nil_id { let attribute_value_id = OutputSocket::component_attribute_value_for_output_socket_id( ctx, output_socket_id, component_id, ) .await?; AttributeValue::set_component_prototype_id( ctx, attribute_value_id, attribute_prototype.id, None, ) .await?; affected_attribute_value_ids.push(attribute_value_id); } else { // remove the existing attribute prototype and arguments if let Some(existing_proto) = AttributePrototype::find_for_output_socket(ctx, output_socket_id).await? { delete_attribute_prototype_and_args(ctx, existing_proto).await?; } OutputSocket::add_edge_to_attribute_prototype( ctx, output_socket_id, attribute_prototype.id, EdgeWeightKind::Prototype(key), ) .await?; } } else { return Err(FuncAuthoringError::NoOutputLocationGiven(func_id)); } if !affected_attribute_value_ids.is_empty() { ctx.add_dependent_values_and_enqueue(affected_attribute_value_ids) .await?; } Ok(attribute_prototype.id) }

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