Overview
The Registry is the central storage and management system for all plugin features. It provides type-safe methods to register, resolve, and serialize credentials, tools, and models. Every plugin instance has an internal registry that tracks all registered features.
Registry Architecture
The registry is created automatically when you create a plugin:
const plugin = await createPlugin ({ /* ... */ })
// Internally creates: const registry = createRegistry(pluginDefinition)
Internal Structure
interface RegistryStore {
plugin : PluginRegistry
credential : Map < string , CredentialDefinition >
model : Map < string , ModelDefinition >
tool : Map < string , ToolDefinition >
}
Each feature type is stored in a separate Map with the feature name as the key.
Registry Interface
The registry provides three core operations:
1. Register Features
Store feature definitions in the registry:
registry . register ( "credential" , credentialDefinition )
registry . register ( "tool" , toolDefinition )
registry . register ( "model" , modelDefinition )
2. Resolve Features
Retrieve feature definitions by type and name:
const credential = registry . resolve ( "credential" , "github_api_key" )
const tool = registry . resolve ( "tool" , "send_message" )
const model = registry . resolve ( "model" , "gpt-4" )
3. Serialize Registry
Convert the entire registry to a JSON-serializable format:
const serialized = registry . serialize ()
// Returns: { plugin: { ...metadata, credentials: [...], tools: [...], models: [...] } }
Registration
How Registration Works
When you call addCredential, addTool, or addModel on your plugin:
The definition is validated against its schema
The validated definition is passed to registry.register()
The registry stores it in the appropriate Map
// From plugin.ts
addTool : ( tool : ToolDefinition ) => {
// 1. Validate
const definition = ToolDefinitionSchema . parse ( tool )
// 2. Register
registry . register ( "tool" , definition )
}
Implementation
// From registry.ts
function register ( type : FeatureType , feature : Feature ) : void {
store [ type ]. set ( feature . name , feature )
}
The implementation is simple: features are stored in a Map with their name as the key.
Type Safety
The register function is overloaded to ensure type safety:
register : {
( type : "credential" , feature : CredentialDefinition ) : void
( type : "model" , feature : ModelDefinition ) : void
( type : "tool" , feature : ToolDefinition ) : void
}
This ensures you can’t accidentally register a tool as a credential, for example.
Resolution
How Resolution Works
When the SDK needs to execute a tool or authenticate a credential:
The registry looks up the feature by type and name
If found, returns the feature definition
If not found, throws an error
// From plugin.ts
channel . on ( "invoke_tool" , async ( message ) => {
const { tool_name } = message
// Resolve the tool definition
const definition = registry . resolve ( "tool" , tool_name )
// Execute the tool
const result = await definition . invoke ({ /* ... */ })
})
Implementation
// From registry.ts
function resolve ( type : FeatureType , featureName : string ) : Feature {
const feature = store [ type ]. get ( featureName )
// Assert that feature exists
assert ( feature , `Feature " ${ featureName } " not registered` )
return feature
}
Error Handling
If you try to resolve a feature that doesn’t exist:
try {
const tool = registry . resolve ( "tool" , "nonexistent_tool" )
} catch ( error ) {
// Error: Feature "nonexistent_tool" not registered
}
Type Safety
Like register, the resolve function is overloaded:
resolve : {
( type : "credential" , featureName : string ) : CredentialDefinition
( type : "model" , featureName : string ) : ModelDefinition
( type : "tool" , featureName : string ) : ToolDefinition
}
This ensures the return type matches the feature type you’re resolving.
Serialization
Purpose
Serialization converts the registry into a JSON-serializable format for:
Registration : Sending plugin definition to Hub Server (debug mode)
Export : Writing definition.json for release deployments
Inspection : Debugging and validating plugin configuration
How Serialization Works
const { plugin } = registry . serialize ()
// Structure:
{
plugin : {
// Plugin metadata
name : "my-plugin" ,
display_name : { en_US : "My Plugin" },
author : "Jane Doe" ,
email : "[email protected] " ,
version : "1.0.0" ,
// Serialized features (without functions)
credentials : [
{ name: "api_key" , display_name: { en_US: "API Key" }, /* ... */ }
],
tools : [
{ name: "send_message" , display_name: { en_US: "Send Message" }, /* ... */ }
],
models : [
{ name: "gpt-4" , display_name: { en_US: "GPT-4" }, /* ... */ }
]
}
}
Implementation
// From registry.ts
serialize : () => {
assert ( store . plugin , "Plugin is not registered" )
return {
plugin: Object . assign ( store . plugin , {
credentials: Array . from ( store . credential . values ()). map ( serializeFeature ),
models: Array . from ( store . model . values ()). map ( serializeFeature ),
tools: Array . from ( store . tool . values ()). map ( serializeFeature ),
}),
}
}
Feature Serialization
The serializeFeature utility removes functions from feature definitions:
// From utils/serialize-feature.ts
export const serializeFeature = ( feature : Record < string , unknown >) => {
return Object . keys ( feature ). reduce < Record < string , JsonValue >>(( finale , key ) => {
const value = feature [ key ]
// Skip function properties
if ( isFunction ( value )) {
return finale
}
return Object . assign ( finale , { [key]: value as JsonValue })
}, {})
}
This ensures that executable code (like invoke and authenticate functions) are excluded from the serialized output.
Why Remove Functions?
Security : Functions can’t be safely transmitted over the network
JSON Compatibility : Functions aren’t JSON-serializable
Separation : The definition.json describes what the plugin does, not how
Registry Access
While you typically interact with the registry through plugin methods, you can access it directly:
// Plugin metadata
const pluginInfo = registry . plugin
// Check what's registered
const hasCredential = store . credential . has ( "api_key" )
const toolCount = store . tool . size
// Serialize for export
const definition = registry . serialize ()
await Bun . write ( "definition.json" , JSON . stringify ( definition . plugin , null , 2 ))
The registry’s internal store is not directly exposed in the public API. Access features through the resolve and serialize methods.
Feature Types
The registry supports three feature types:
type FeatureType = "credential" | "model" | "tool"
Data sources (data_source) are defined in the type system but currently commented out in the implementation. Future versions may add support for data sources.
Example: Complete Registry Flow
import { createPlugin } from "@choiceopen/atomemo-plugin-sdk-js"
const plugin = await createPlugin ({
name: "example-plugin" ,
display_name: { en_US: "Example Plugin" },
description: { en_US: "Demonstrates registry usage" }
})
// Register features (internally uses registry.register)
plugin . addCredential ({
name: "api_key" ,
display_name: { en_US: "API Key" },
schema: {
type: "object" ,
properties: {
key: { type: "string" }
}
}
})
plugin . addTool ({
name: "fetch_data" ,
display_name: { en_US: "Fetch Data" },
parameters: { type: "object" , properties: {} },
credentials: [ "api_key" ],
invoke : async ({ args }) => {
return { data: "example" }
}
})
plugin . addModel ({
name: "gpt-4" ,
display_name: { en_US: "GPT-4" },
model_type: "llm" ,
credentials: [ "api_key" ]
})
// When plugin runs:
await plugin . run ()
// Internally, the SDK:
// 1. Connects to Hub Server
// 2. Serializes the registry
// 3. Sends plugin definition (debug mode)
// 4. Listens for tool invocations:
channel . on ( "invoke_tool" , async ( message ) => {
// Resolves tool from registry
const tool = registry . resolve ( "tool" , message . tool_name )
// Executes tool
const result = await tool . invoke ({ args: message })
// Sends response
channel . push ( "invoke_tool_response" , result )
})
Best Practices
Feature names must be unique within each type. Use descriptive names that clearly identify the feature.
Always register all features before calling plugin.run(). Features registered after the plugin starts won’t be available.
When resolving features, handle cases where the feature might not exist (though this shouldn’t happen if you’ve registered everything correctly).
Validate before registering
The SDK validates features automatically, but you can catch validation errors during registration to provide better error messages.
Internal Implementation Details
Registry Creation
// From registry.ts
export function createRegistry ( plugin : PluginRegistry ) : Registry {
const store : RegistryStore = {
plugin ,
credential: new Map (),
model: new Map (),
tool: new Map (),
}
// Return interface with register, resolve, serialize methods
return { plugin: store . plugin , register , resolve , serialize }
}
Plugin Definition Type
// Excludes transporterOptions from serialization
type PluginRegistry = Omit < PluginDefinition , "transporterOptions" >
The transporter options are used for WebSocket configuration but aren’t part of the plugin definition sent to the Hub Server.
Next Steps
Plugins Learn about the plugin lifecycle and architecture
Transporter Understand how features are transmitted to Hub Server