While the BaseEngine class is a singleton, normally, it is possible to have multiple instances of the class in an application if the class is used in multiple contexts that have different providers.
All agent categories, cached during Config(). Used for hierarchical resolution of assignment strategies and default storage accounts (category → parent → root).
All MJ: AI Agent Channels interactive-channel registry rows, cached during Config().
Each row declares a realtime channel surface (e.g. Whiteboard) with its server/client
plugin class keys and transport type; only rows with IsActive may be attached to a
session. Small metadata table — filter client-side (e.g. c => c.IsActive) rather than
issuing RunViews.
Gets all agent-to-client-tool junction records.
All MJ: AI Agent Co Agents affinity rows, cached during Config(). Each row relates an
owning agent (CoAgentID — for Type='CoAgent', a Realtime-type co-agent) to EITHER a
specific paired agent (TargetAgentID) or a whole agent type (TargetAgentTypeID),
with Type naming the relationship nature (only 'CoAgent' is implemented today; the
other values are reserved). Ordered for pickers via Sequence; IsDefault marks the
co-agent's default target (agent rows) or the type's default co-agent (type rows);
Status='Disabled' rows are kept for audit but must be ignored by resolution. A
co-agent with ZERO Active 'CoAgent' rows is universal (it can front any target).
Small metadata table — filter client-side (e.g. UUIDsEqual(row.CoAgentID, ...))
rather than issuing RunViews. NOTE: the dataset is registered conditionally (see
Config()); on a clean-install CodeGen bootstrap this is [] until the entity exists.
Cached array of AI Agent Configurations loaded from the database. These define semantic presets for agents (e.g., "Fast", "High Quality").
Gets all agent-modality mappings
Agent ↔ Skill grant rows ("MJ: AI Agent Skills"), used when an agent's AcceptsSkills is 'Limited'.
Returns true if ALL configured properties loaded successfully. Useful as a quick health check after engine startup.
Gets all client tool definitions (the catalog of reusable tools).
Returns a COPY of the metadata configs array for the engine. This is a copy so you can't modify the original configs by modifying this array.
O(1) lookup of a configuration by ID. Lazily built from Configurations; reset on reload.
Returns the context user set for the object, this is set via the Config() method.
Observable that emits when any data property changes due to a refresh. Subscribe to this to react to engine data updates (e.g., sync Angular observables).
Events are emitted after data is refreshed in response to BaseEntity save/delete events. The event includes the full config and the new data array.
Returns a read-only snapshot of all engine property load states. Each entry maps a property name to its load status, including entity/dataset name, row count, success/failure flag, and error message if applicable. Used by dev tools for diagnostics and health monitoring.
ProtectedEngineControls the default RunView ResultType for all entity configs loaded by this engine. Override in subclasses to change the default for the entire engine without modifying each individual config entry.
Individual configs can still override this via their own ResultType property.
ProtectedEntityOverridable property to set the debounce time for entity events. Default is 1500 milliseconds (1.5 seconds). This debounce time is used when immediate array mutations cannot be applied (e.g., when Filter, OrderBy, or AdditionalLoading overrides are present) and a full view refresh is required.
Note: When immediate mutations ARE possible (no Filter, OrderBy, or AdditionalLoading override), updates happen synchronously without any debounce delay.
The ID of the "Inference Provider" vendor-type definition, or undefined if no such
definition is loaded. Memoized — the underlying lookup is a linear scan of
VendorTypeDefinitions, but this getter caches the result for the lifetime of a
loaded engine (reset on reload). Prefer IsInferenceProvider for the common
"is this model-vendor an inference provider?" check.
True when the engine loaded successfully but all entity configs were skipped because the current user lacks read permissions. Accessor properties will throw PermissionConstrainedError if accessed in this state. Check this flag first to degrade gracefully.
Convenience method to return only the Language Models. Loads the metadata if not already loaded.
Returns true if the data has been loaded, false otherwise.
Returns the loading subject. You can call await Config() and after Config() comes back as true that means you're loaded. However you can also directly subscribe to this subject to get updates on the loading status.
ProtectedMaxMaximum number of retries for an event-triggered config refresh that failed transiently. Overridable by subclasses that want more or less persistence.
Gets all AI modalities (Text, Image, Audio, Video, File, Embedding, etc.)
Gets all model-modality mappings
O(1) lookup of a model by ID. Lazily built from Models; reset on reload.
Use instead of Models.find(m => UUIDsEqual(m.ID, id)) in hot paths.
O(1) lookup of a model type by ID. Lazily built from ModelTypes; reset on reload.
Model-vendor records grouped by ModelID. Lazily built from ModelVendors; reset on reload.
Use instead of ModelVendors.filter(mv => UUIDsEqual(mv.ModelID, id)) in hot paths.
NOTE: the per-model MJAIModelEntityExtended.ModelVendors property is the preferred
accessor when you already hold the model entity; this index serves callers that only
have a ModelID.
List of entity names that were skipped due to permission denial. Empty if not permission-constrained. Useful for logging/diagnostics.
Prompt-model associations grouped by PromptID. Lazily built from PromptModels; reset on reload.
Use instead of PromptModels.filter(pm => UUIDsEqual(pm.PromptID, id)) in hot paths.
Returns the metadata provider to use for the engine. If a provider is set via the Config method, that provider will be used, otherwise the default provider will be used.
Returns the RunView provider to use for the engine. This is the same underlying object as the
All scoped prompt configs (MJ: Scoped Prompt Configs). Cached like ScopedPromptParts; the run-settings sibling of parts — resolved by scope + overlaid onto AIPromptParams by the ScopedPromptConfigResolver. See plans/scoped-prompt-components.
All scoped prompt parts (MJ: Scoped Prompt Parts). Cached like AgentNotes; resolved by scope + assembled into role-faithful messages by the ScopedPromptPartInjector. See plans/scoped-prompt-components.
Skill → Action bundling rows ("MJ: AI Skill Actions"), cached during Config().
Per-user / per-role skill permission grants ("MJ: AI Skill Permissions"), cached during Config(). Consumed by AISkillPermissionHelper (open-by-default runtime gate).
All AI Skills (capability bundles), cached during Config(). Filter by Status yourself or use GetSkillsForAgent for the full agent-gating resolution.
Skill → sub-agent bundling rows ("MJ: AI Skill Sub Agents"), cached during Config().
O(1) lookup of a vendor by ID. Lazily built from Vendors; reset on reload.
StaticInstanceAdds a dynamic metadata configuration at runtime.
The metadata configuration to add
OptionalcontextUser: UserInfoThe context user information
ProtectedAdditionalSubclasses can override this method to perform additional loading tasks
OptionalcontextUser: UserInfoChecks if an agent supports any non-text input modalities (images, audio, video, files). This is used to determine if attachment upload should be enabled in the UI.
The agent ID
True if the agent supports at least one non-text input modality
Checks if an agent supports a specific modality for a given direction. If no agent modalities are configured, defaults to text-only.
The agent ID
The modality name (e.g., 'Image', 'Audio')
'Input' or 'Output'
True if the agent supports this modality
ProtectedapplyApplies an immediate array mutation based on the entity event type. This is faster than running a full view refresh for simple add/update/delete operations.
On save, the cached entry is a clone owned by this engine's provider — not the saver's entity instance. Storing the saver's instance would pin the saver's provider (often a per-request provider) inside the engine's cache for the engine's full lifetime, which leaks the provider and all its associated state.
The configuration for the property being mutated
The entity event containing the affected entity and event type
ProtectedapplyRemoves a deleted record from the engine's in-memory arrays using the primary key values from the remote-invalidate event payload. No server round-trip needed.
true if successfully applied to all matching configs, false if fallback is needed
ProtectedapplyApplies record data from a remote-invalidate event directly to the engine's in-memory arrays. Creates a BaseEntity instance, loads the JSON data into it, then updates the matching config arrays — same as applyImmediateMutation but from serialized data instead of a live entity.
true if successfully applied to all matching configs, false if fallback is needed
ProtectedbeginOpens a new full-refresh "generation" for a property and returns its token. Each call bumps the property's monotonic counter, so a token is the latest iff no later refresh for that property has begun since. See _configRefreshGeneration.
Utility method that will cache the result of a prompt in the AI Result Cache entity.
Uses this engine's bound provider (this.ProviderToUse) so the cache record is written
to the same connection the engine was configured against — multi-tenant correct in
server contexts where each request's engine is bound to a per-request provider.
ProtectedcanDetermines if an immediate array mutation can be used instead of running a full view refresh. Immediate mutations are only safe when:
The configuration to check
OptionalskipAdditionalLoadingCheck: booleanWhen true, skips the AdditionalLoading override check. Use this when the caller will invoke AdditionalLoading() itself after applying mutations (e.g., applyRemoteRecordData applies all config mutations then calls AdditionalLoading).
true if immediate mutation is safe, false if a full view refresh is needed
Checks if a user has permission to delete an agent.
The ID of the agent to check
The user to check permissions for
True if the user can delete the agent
Checks if a user can delete a skill (owner or explicit grant).
Checks if a user has permission to edit an agent.
The ID of the agent to check
The user to check permissions for
True if the user can edit the agent
Checks if a user can edit a skill (owner or explicit grant).
Checks if a user has permission to run an agent.
The ID of the agent to check
The user to check permissions for
True if the user can run the agent
Checks if a user can run (request/activate) a skill (open-by-default).
Checks if a user has permission to view an agent.
The ID of the agent to check
The user to check permissions for
True if the user can view the agent
Checks if a user can view a skill (open-by-default).
ProtectedCheckProtectedCheckAll-or-nothing permission gate: checks CanRead on every entity config. If ANY
entity is denied, ALL configs are skipped — the engine is marked permission-constrained
and its data arrays are set to empty []. This prevents noisy permission-denied errors
and endless retry loops for users with limited permissions (e.g., org-scoped SaaS roles).
On the server side with a system user (who has all permissions), this method returns the original configs unchanged — no behavior change for privileged users.
The original configs array (all permissions pass) or an empty array (any denied)
This method will check the result cache for the given params and return the result if it exists, otherwise it will return null if the request is not cached.
the fully populated prompt to check the cache for
ProtectedclassifyClassifies a single entity event against a single config's backing array:
For deletes, the by-key membership check uses the event payload's pre-delete OldValues snapshot — BaseEntity.Delete() calls NewRecord() right after raising the event, which wipes field values and REGENERATES the primary key, so the live entity's key can never match the deleted row by the time the debounced handler runs.
Clears the agent permissions cache. Call this after modifying permissions.
ProtectedcloneCreates a fresh BaseEntity owned by this engine's provider and populates it from the given source entity's field values. Used by applyImmediateMutation to avoid pinning the source entity's provider inside this engine's cache.
Configures the engine by loading metadata from the database. Subclasses must implement this method to define their configuration behavior.
Note: This method is called by ConfigEx() - prefer using ConfigEx() directly for new code as it provides more flexible configuration options.
OptionalforceRefresh: booleanOptionalcontextUser: UserInfoOptionalprovider: IMetadataProviderExtended configuration method with object-based options. This provides a more flexible API compared to Config() with positional parameters.
Internally calls Config() after setting up options that Load() can access.
Optionaloptions: ConfigExOptionsConfiguration options object
Promise that resolves when configuration is complete
ProtectedconfigTrue when the config's last load attempt left it in a successfully-loaded state. Reads the same map entry HandleSingleViewResult writes — a transient failure (network, server restart) records loadedSuccessfully=false; a permission denial is recorded as loaded-empty (true) and is deliberately NOT retryable.
ProtectedContextReturns false ONLY when we can positively determine that the effective user
lacks Read permission on entityName. Unknown cases (no entity name, no
resolvable user, entity not in metadata) return true — so the default is to
treat a failure as transient/retryable and server-side system-user loads
(full access) are unaffected.
Used by HandleSingleViewResult to classify a FAILED config load: a load that failed because the user can't read the entity is a PERMANENT condition (a retry will never succeed for this role), so the engine should load that property empty rather than loop on "not marking as loaded" — which is what hangs the Explorer shell for a restricted / app-scoped user (e.g. a magic-link guest). Security is unaffected: the user still receives no data.
This is a classifier consulted AFTER a failure, never a predictive pre-skip — so a readable entity is always actually queried, and stale/late client permission metadata can never cause a readable entity to be silently skipped.
ProtectedDebounceThis method handles the debouncing process, by default using the EntityEventDebounceTime property to set the debounce time. Debouncing is done on a per-entity basis, meaning that if the debounce time passes for a specific entity name, the events will be processed. This is done to prevent multiple events from being processed in quick succession for a single entity which would cause a lot of wasted processing.
ALL events raised during the debounce window are buffered and delivered as one batch to ProcessEntityEvents — not just the last one. The refresh-vs-skip decision must be an OR over every coalesced event: judging only the last event would let an already-applied write (e.g., an engine method's in-place save of a cached instance) mask an earlier fresh-instance save the array has never seen.
Override this method if you want to change how debouncing works, such as having variable debounce times per-entity, etc.
ProtectedemitNotifies subscribers of ObserveProperty(propertyName) that the array has changed.
No-op if no one has ever observed this property (BehaviorSubject not created).
Called from the array mutation sites in BaseEngine.
Ensures the engine is loaded before the caller reads engine state. This is
the right call to make at every consumption point — especially for engines
registered with @RegisterForStartup({ deferred: true }) whose initial load
runs in the background after app boot.
Idempotent: if the engine is already loaded, returns immediately. If a load
is in flight (e.g. the deferred startup or another consumer triggered it),
returns the same in-progress promise rather than starting a second load —
BaseEngine.Load handles this internally via _loadingSubject.
Equivalent to this.Config(false) but reads more clearly at call sites:
await AIEngineBase.Instance.EnsureLoaded();
const models = AIEngineBase.Instance.Models;
OptionalcontextUser: UserInfoOptional context user (server-side only)
Optionalprovider: IMetadataProviderOptional metadata provider override
ProtectedfindFinds an entity in the array by matching all primary key columns. Supports composite primary keys by comparing all PrimaryKey fields from EntityInfo.
The array of entities to search
The entity to find (using its primary key values)
The index of the matching entity, or -1 if not found
Gets all agents a user has access to with a specific permission level.
The user to check permissions for
The minimum permission level required ('view', 'run', 'edit', or 'delete')
Array of agents the user can access
Gets all skills a user can access with a specific permission level.
Gets the active cost configuration for a specific model and vendor combination
The ID of the AI model
The ID of the vendor
'Realtime' or 'Batch' (defaults to 'Realtime')
The active MJAIModelCostEntity or null if none found
Gets aggregated attachment limits for an agent, suitable for passing to UI components. Combines limits from all supported input modalities (Image, Audio, Video, File). Uses the most restrictive limits across all modalities for the aggregate values.
The ID of the agent
OptionalmodelId: stringOptional model ID to include model-specific limits in the resolution
Aggregated attachment limits ready for UI component configuration
Gets a specific configuration preset by agent ID and preset name
The agent ID
The preset name (e.g., "Fast", "HighQuality")
The configuration preset, or undefined if not found
Gets all configuration presets for a specific agent
The agent ID
If true, only returns Active status presets (default: true)
Array of configuration presets sorted by Priority
Gets all modalities supported by an agent for a given direction
The agent ID
'Input' or 'Output'
Array of modality entities the agent supports
Resolves the effective limits for a specific modality for an agent. Uses precedence chain: Agent → Model → System → Defaults.
The ID of the agent
The modality name (e.g., 'Image', 'Audio', 'Video', 'File')
OptionalmodelId: stringOptional model ID to check model-specific limits (falls back to system defaults if not provided)
The resolved modality limits with source information
Gets a specific agent step by ID
The ID of the step
The step or null if not found
Gets agent steps for a specific agent, optionally filtered by status
The ID of the agent
Optionalstatus: stringOptional status filter ('Active', 'Pending', 'Disabled')
Array of agent steps
Gets all input modality names supported by an agent (for UI display/filtering)
The agent ID
Array of modality names the agent accepts as input
Resolves the subset of GetSkillsForAgent that the agent may self-activate —
i.e. the skills eligible to appear in the agent's prompt catalog and to be activated by an
agent-initiated Skill step, without an explicit user request.
Self-activation is governed by the double activation gate (added in v5.45), on top of all availability gates enforced by GetSkillsForAgent:
agent.SkillActivationMode === 'Auto' — the agent side of the gate. The default is
'RequestedOnly', meaning the agent's prompt catalog is empty and skills only enter its
runs via explicit user requests (/skill mentions → ExecuteAgentParams.requestedSkillIDs).skill.ActivationMode === 'Auto' — the skill side. The default is 'RequestedOnly',
meaning the skill never appears in ANY agent's catalog regardless of agent posture.Auto × Auto is the deliberately-configured "super agent" posture — an agent that may expand
its own tool surface at runtime. Because both defaults are 'RequestedOnly', that posture
always requires two explicit opt-ins and can never arise accidentally ("skill leakage").
The requested path is NOT gated by ActivationMode — a user's explicit /skill request
for a RequestedOnly skill is honored (subject to the availability gates); use
GetSkillsForAgent for that path.
The agent to resolve the self-activatable catalog for.
Optionaluser: UserInfoOptional user; when present, additionally filters to skills the user can Run (same semantics as GetSkillsForAgent).
Active skills the agent may self-activate (empty when either side of the double gate is 'RequestedOnly', or when no availability gate passes).
Gets the client tool definitions linked to a specific agent, sorted by priority. Joins through the junction table to return full tool definition entities.
ProtectedGetRetrieves engine-loaded data for a config property by name. This is the canonical accessor for engine getter properties — it checks the data map for permission denial and throws PermissionConstrainedError with the specific denied entity name(s) if the config was skipped.
Subclasses should use this in every getter that exposes engine-loaded data:
public get Models(): MJAIModelEntityExtended[] {
return this.GetConfigData<MJAIModelEntityExtended>('_models');
}
The config property name (e.g., '_models', '_agents'), matching the PropertyName used in the engine's Config() params array.
The data array for the property, or an empty array if not yet loaded.
Returns the inheritance chain for a configuration, starting with the specified configuration and walking up through parent configurations to the root.
The chain is ordered from most-specific (the requested configuration) to least-specific (the root parent with no ParentID).
Results are cached for performance. Cache is invalidated when configurations are reloaded via Config().
The ID of the configuration to get the chain for
Array of MJAIConfigurationEntity objects representing the inheritance chain, or empty array if the configuration is not found
// Single configuration with no parent
const chain = AIEngine.Instance.GetConfigurationChain('config-a');
// Returns: [ConfigA]
// Child -> Parent -> Grandparent chain
const chain = AIEngine.Instance.GetConfigurationChain('child-config');
// Returns: [ChildConfig, ParentConfig, GrandparentConfig]
// Usage in model selection - first config in chain with a match wins
const chain = AIEngine.Instance.GetConfigurationChain(configId);
for (const config of chain) {
const models = promptModels.filter(pm => pm.ConfigurationID === config.ID);
if (models.length > 0) return models;
}
// Fall back to null-config models if no match in chain
Gets a specific configuration parameter value
The ID of the configuration
The name of the parameter
The parameter entity or null if not found
Gets configuration parameters for a specific configuration
The ID of the configuration
Array of configuration parameters for the specified configuration
Returns all configuration parameters for a configuration, including inherited parameters from parent configurations. Child parameters override parent parameters with the same name (case-insensitive match).
The inheritance chain is walked from root to child, so child values take precedence over parent values for parameters with the same name.
The ID of the configuration to get parameters for
Array of MJAIConfigurationParamEntity objects, with child overrides applied. Returns empty array if configuration is not found.
Gets credential bindings for a specific target, filtered by binding type and sorted by priority. Only returns active bindings.
The type of binding: 'Vendor', 'ModelVendor', or 'PromptModel'
The ID of the target entity (AIVendorID, AIModelVendorID, or AIPromptModelID)
Array of active credential bindings sorted by Priority (lower = higher priority)
Gets the default configuration preset for an agent
The agent ID
The default preset, or undefined if none exists
The Global Object Store is a place to store global objects that need to be shared across the application. Depending on the execution environment, this could be the window object in a browser, or the global object in a node environment, or something else in other contexts. The key here is that in some cases static variables are not truly shared because it is possible that a given class might have copies of its code in multiple paths in a deployed application. This approach ensures that no matter how many code copies might exist, there is only one instance of the object in question by using the Global Object Store.
Convenience method to return the highest power LLM model for a given vendor. Loads the metadata if not already loaded.
OptionalvendorName: stringif provided, filters to only consider models from the specified vendor, otherwise considers all models
OptionalcontextUser: UserInfoConvenience method to returns the highest power model for a given vendor and model type. Loads the metadata if not already loaded.
if set to null, undefined, or an empty string, then all models of the specified type are considered
the type of model to consider
OptionalcontextUser: UserInforequired on the server side
Gets a modality by name (case-insensitive)
The modality name (e.g., 'Text', 'Image', 'Audio', 'Video', 'File')
The modality entity or undefined if not found
Gets all modalities supported by a model for a given direction
The model ID
'Input' or 'Output'
Array of modality entities the model supports
Resolves the effective limits for a specific modality for a model. Uses precedence chain: Model → System → Defaults.
The ID of the model
The modality name (e.g., 'Image', 'Audio', 'Video', 'File')
The resolved modality limits with source information
Gets paths originating from a specific step
The ID of the origin step
Array of paths from the step
Returns the ActionIDs bundled into a skill (via "MJ: AI Skill Actions"). Callers resolve
the full MJActionEntity objects from their own Action cache (e.g. ActionEngineServer)
to avoid a cross-package dependency here.
Resolves the set of skills a given agent may activate, honoring the three-layer gate: MJAIAgentEntityExtended.AcceptsSkills on the agent, MJAISkillEntity.Status on the catalog entry, and (when AcceptsSkills is 'Limited') MJAIAgentSkillEntity.Status on the grant.
AcceptsSkills = 'None' (default) → no skills, regardless of catalog or grants.AcceptsSkills = 'All' → every Active skill in the catalog.AcceptsSkills = 'Limited' → only Active skills with an Active MJ: AI Agent Skills grant for this agent.When user is supplied, the agent-allowed set is additionally intersected with the user's
run-permission (open-by-default via AISkillPermissionHelper) — i.e. only skills the
user is permitted to request survive. This is the single call the /skill picker and the
server-side RequestedSkills intersection guard use. Omit user for pure agent-gating (e.g.
resolving the full activatable catalog independent of who is asking).
The agent to resolve available skills for.
Optionaluser: UserInfoOptional user; when present, filters to skills the user can Run.
MJAISkillEntity[] - Active skills the agent may activate (empty if AcceptsSkills is 'None').
Returns the sub-agent IDs bundled into a skill (via "MJ: AI Skill Sub Agents"). Callers
resolve the full MJAIAgentEntityExtended objects via this.Agents / GetAgentByID.
Returns the sub-agents for a given agent ID, optionally filtering by status. Includes both child agents (ParentID relationship) and related agents (AgentRelationships).
The ID of the parent agent to get sub-agents for
Optionalstatus: "Active" | "Pending" | "Disabled"Optional status to filter sub-agents by (e.g., 'Active', 'Inactive'). If not provided, all sub-agents are returned.
OptionalrelationshipStatus: "Active" | "Pending" | "Revoked"Optional status to filter agent relationships by. Defaults to 'Active' if not provided.
MJAIAgentEntityExtended[] - Array of sub-agent entities matching the criteria (deduplicated by ID).
Gets all effective permissions a user has for a specific agent.
The ID of the agent
The user to check permissions for
Object containing all permission flags and ownership status
Gets all effective permissions a user has for a specific skill.
ProtectedHandleThis method handles the individual base entity event. For events that can use immediate array mutations (no Filter, OrderBy, or AdditionalLoading override), processing happens synchronously without debounce. For events that require full view refresh, debouncing is applied to batch rapid successive changes.
Override this method if you want to have a different handling for the filtering of events that are debounced or if you don't want to debounce at all you can do that in an override of this method.
ProtectedHandleSubclasses of BaseEngine can override this method to handle individual MJGlobal events. This is typically done to optimize the way refreshes are done when a BaseEntity is updated. If you are interested in only BaseEntity events, override the HandleIndividualBaseEntityEvent method instead as this method primarily serves to filter all the events we get from MJGlobal and only pass on BaseEntity events to HandleIndividualBaseEntityEvent.
ProtectedHandleHandles remote-invalidate events from cross-server cache invalidation. These events are fired by GraphQLDataProvider when it receives a cache invalidation notification via GraphQL subscription (originating from Redis pub/sub on another server).
When the event payload includes recordData (the saved entity as JSON), the engine applies the change directly to its in-memory array — no server round-trip needed. For delete events or events without recordData, falls back to LoadSingleConfig.
ProtectedHandleHandles the result of a single view load.
OptionalcontextUser: UserInfoAll BaseEngine sub-classes get an implementation of IStartupSink so they can be set the auto start in their app container, if desired, simply by adding the
OptionalcontextUser: UserInfoOptionalprovider: IMetadataProviderProtectedhasChecks if the current instance has overridden the AdditionalLoading method. We do this by comparing the method to the base class's method.
true if AdditionalLoading is overridden, false if using the base implementation
Checks if any credential bindings exist for a specific target.
The type of binding: 'Vendor', 'ModelVendor', or 'PromptModel'
The ID of the target entity
True if at least one active binding exists
ProtectedisChecks if the exact entity object reference is already in the config's data array. Used to skip unnecessary refreshes for UPDATE events where the object was mutated in place.
The configuration to check
The entity to look for
true if the exact object reference is already in the array
ProtectedisChecks if an entity is in the config's data array by object reference OR by primary key match. Used for DELETE events where we need to know if the entity still exists in the array.
For deletes, pass preDeleteValues (the event payload's OldValues snapshot): by the time
the debounced handler runs, BaseEntity.Delete() has already called NewRecord(), which wipes
the entity's fields and regenerates its primary key — so a by-key check against the live
entity can never match the deleted row. Same hazard (and same OldValues workaround) as
LocalCacheManager.HandleBaseEntityEvent.
The configuration to check
The entity to look for
OptionalpreDeleteValues: Record<string, unknown>Pre-delete field snapshot (delete event payload's OldValues)
true if the entity is in the array (by reference or by primary key)
Returns true when the given model-vendor record represents an inference provider (a service that runs the model) rather than a model developer (the company that trained it). This is the canonical check — use it instead of re-scanning VendorTypeDefinitions at each call site.
Resolution: compares modelVendor.TypeID against the memoized "Inference Provider"
type ID. If that type isn't loaded (should be rare), falls back to "anything that is
not explicitly a Model Developer is treated as an inference provider".
ProtectedisTrue when generation is still the most recent token handed out by
beginConfigRefresh for propertyName — i.e. no newer full refresh for this
property has started since. A refresh whose token is stale must NOT commit its results:
a newer refresh was initiated afterward and read a more-recent state.
Check if a specific property was skipped due to permission denial. Forward-compatible with a future partial-loading approach.
ProtectedLoadThis method should be called by sub-classes to load up their specific metadata requirements. For more complex metadata loading or for post-processing of metadata loading done here, overide the AdditionalLoading method to add your logic.
OptionalforceRefresh: booleanOptionalcontextUser: UserInfoProtectedLoadLoads the specified metadata configurations.
The metadata configurations to load
The context user information
OptionalbypassCache: booleanWhen true, bypasses all server-side caching (RunView and dataset) to fetch fresh data
directly from the database. Passed through from Load when forceRefresh is true (i.e., Config(true)).
ProtectedLoadHandles the process of loading multiple entity configs in a single network call via RunViews()
OptionalbypassCache: booleanWhen true, bypasses server-side cache to get fresh data from the database
ProtectedLoadLoads a single metadata configuration.
The metadata configuration to load
The context user information
OptionalbypassCache: booleanWhen true, bypasses server-side cache to get fresh data from the database
ProtectedLoadHandles the process of loading a single config of type 'dataset'.
OptionalbypassCache: booleanWhen true, bypasses server-side cache to get fresh data from the database.
Uses IMetadataProvider.GetDatasetByName with forceRefresh to skip all cache reads,
then IMetadataProvider.CacheDataset to store fresh results for subsequent non-forced calls.
ProtectedLoadHandles the process of loading a single config of type 'entity'.
OptionalbypassCache: booleanWhen true, bypasses server-side cache to get fresh data from the database
ProtectedMarkRecords a config as successfully loaded with an EMPTY result set. Used when a load failed permanently because the context user lacks Read on the entity: the engine exposes an empty array (not a hang) and is marked loaded so shell boot can complete for a restricted role.
Checks if a model supports a specific modality for a given direction
The model ID
The modality name (e.g., 'Image', 'Audio')
'Input' or 'Output'
True if the model supports this modality
ProtectednotifyEmits change notifications for a config whose backing array ALREADY reflects the
entity event — e.g., engine code saved the array's own cached instance in place,
or manually pushed a newly created entity after Save. In those cases
ProcessEntityEvent safely skips the redundant refresh, but the notification
must NOT be skipped: without it, DataChange$ and ObserveProperty subscribers
(and anything derived from them downstream) never learn the array changed and are
stranded on stale state.
Engine subclasses that manually SPLICE a deleted row out of a config's array must call this themselves ('delete') right after splicing — the debounced event handler cannot distinguish "already spliced" from "never matched this config's Filter", so it stays silent for absent rows.
Deliberately does not run AdditionalLoading — the skip paths never did, and engines that maintain their arrays manually own any derived-data updates themselves.
ProtectedNotifyNotify listeners that a data property has changed. Called automatically by HandleSingleViewResult after data refresh and by applyImmediateMutation for array operations. Subclasses can also call this manually when modifying data arrays directly.
The configuration for the property that changed
The current data array
OptionalchangeType: "delete" | "refresh" | "add" | "update"The type of change: 'refresh', 'add', 'update', or 'delete'
OptionalaffectedEntity: BaseEntityFor add/update/delete, the entity that was affected
Returns an Observable for a specific engine array property. Subscribers receive the current array immediately (BehaviorSubject semantics), then re-receive the same array reference whenever the engine mutates it (save, delete, remote-invalidate, refresh).
The BehaviorSubject for a property is lazy-created on first call — engines where no one observes a property pay zero runtime cost.
The name of the backing array property on the engine (e.g. _UserNotifications).
ProtectedOnCalled when another server instance updates cached data that this engine is tracking. Default behavior: reload the affected config from the database.
Engines can override this for custom behavior (e.g., incremental update using the event's CacheChangedEvent.Data payload).
The engine property config whose data changed
The cache change event from the other server
ProtectedProcessBack-compat single-event wrapper around ProcessEntityEvents. The debounced pipeline delivers full batches to ProcessEntityEvents — override THAT method to change event-processing behavior; this wrapper exists for subclasses/tests that process one event at a time.
ProtectedProcessDoes the actual work of processing all entity events coalesced into one debounce window. Not called directly from the event handler because we first debounce the events, which also introduces a delay that is usually desirable so processing happens outside the scope of any transaction processing that originated the events.
Per matching config, the decision is an OR over the whole batch:
A transiently-failed refresh schedules a bounded retry via scheduleEventRefreshRetry — without it, the consumed debounce event would leave observers permanently stale until an unrelated event arrived.
This is the best method to override if you want to change the actual processing of entity events but do NOT want to modify the debouncing behavior.
Returns true if the specified property loaded successfully during engine startup. Returns false if the property failed to load (e.g., RunView error) or was never loaded. Consumers can use this to detect partial load failures and trigger recovery.
Refreshes the permissions cache for a specific agent.
The ID of the agent to refresh
The user context for server-side operations
Refreshes all items
Refreshes a specific item.
The name of the property to refresh
Refreshes the skill permissions cache. Call after modifying permissions.
ProtectedRegisterRegisters cross-server cache change callbacks for entity configs. When another server instance updates cached data for an entity this engine tracks, the engine will automatically reload the affected config.
This enables multi-server deployments to keep engine in-memory arrays synchronized without polling. Requires a Redis-backed storage provider with pub/sub enabled (via RedisLocalStorageProvider.StartListening).
The entity configurations to register callbacks for
Removes a dynamic metadata configuration at runtime.
The name of the property to remove
ProtectedscheduleSchedules a bounded, backed-off retry of a config refresh that failed transiently during entity-event processing. Without this, one failed RunView after a save would permanently strand every observer on stale data — the debounced event is already consumed, so nothing else re-runs the refresh until an unrelated event for the same entity arrives.
At most one retry is pending per property at a time; a retry that succeeds notifies observers through the normal HandleSingleViewResult → NotifyDataChange path. Permission denials never reach here (HandleSingleViewResult marks them loaded-empty).
The config whose refresh failed
1-based attempt number; delays back off linearly (2s, 4s, ...)
ProtectedSetInternal method to set the provider when an engine is loaded. Once this engine instance has a provider bound, subsequent calls are no-ops — preventing transient per-request providers from displacing the persistent provider that first bound to this connection. The cache key is the connection (not the object), so the first persistent provider to load an engine for a connection "owns" the engine for that connection's lifetime.
ProtectedSetupThis method is responsible for registering for MJGlobal events and listening for BaseEntity events where those BaseEntity are related to the engine's configuration metadata. The idea is to auto-refresh the releated configs when the BaseEntity is updated.
ProtectedsyncSyncs an entity change to the LocalCacheManager for a config with CacheLocal enabled. This ensures that IndexedDB/localStorage stays in sync with the engine's in-memory array.
Only called for configs WITHOUT Filter/OrderBy (immediate mutation path). Filtered/sorted configs use debounced refresh which handles its own caching.
The configuration for the property being synced
The entity event containing the affected entity and event type
ProtectedTryHelper method for sub-classes to have a single line of code that will make sure the data is loaded before proceeding and will throw an error if not loaded.
ProtectedUpgradeUtility method to upgrade an object to a BaseEnginePropertyConfig object.
Protected StaticcheckProtected StaticgetReturns the singleton instance of the class. If the instance does not exist, it is created and stored in the Global Object Store. If className is provided it will be used as part of the key in the Global Object Store, otherwise the actual class name will be used. NOTE: the class name used by default is the lowest level of the object hierarchy, so if you have a class that extends another class, the lowest level class name will be used.
OptionalclassName: stringStaticGetReturns the cached engine instance for this engine subclass on the connection the given
provider points to, creating one if none exists yet. Lookup is keyed by the provider's
InstanceConnectionString so multiple provider objects targeting the same connection
share a single cached engine.
StaticRemoveRemoves all cached engine instances for the given connection. Call this when a connection is being torn down (e.g. multi-tenant client logging out) to release the cached engines' memory eagerly. For normal server operation this is rarely needed — the cache is bounded by (distinct connections × engine classes), which is small.
Interface for any singleton class that needs initialization at application startup. Implementing classes must follow the singleton pattern with a static Instance property. Named "Sink" to indicate it receives/handles startup events.