Socket
Book a DemoInstallSign in
Socket

@servicetitan/assist-utils

Package Overview
Dependencies
Maintainers
5
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@servicetitan/assist-utils

Assist utilities for host-MFE communication

latest
npmnpm
Version
1.1.1
Version published
Weekly downloads
170
-44.08%
Maintainers
5
Weekly downloads
 
Created
Source

Atlas Assist Utils Documentation

Overview

The @servicetitan/assist-utils package provides a comprehensive framework for cross-MFE (Micro-Frontend) communication, enabling different applications to share tools and data seamlessly. Built on top of the Atlas Registry system, it uses React hooks and React IoC for dependency injection.

Table of Contents

  • Setup and Installation
  • Architecture Overview
  • Core Concepts
  • Atlas Registry System
  • Tool Registry
  • Data Registry
  • Usage Examples
  • Advanced Patterns
  • Best Practices

Setup and Installation

Provider Hierarchy

The Atlas Registry system requires a specific setup to ensure all applications (Host, MFEs, and TI-Assist) share the same EventBus and registry.

1. Host Application Setup

The Host application is responsible for creating the shared EventBus and registering the Atlas Registry.

First, create the EventBus and provide it via React IoC:

import { Provider } from '@servicetitan/react-ioc';
import { EventBus, EVENT_BUS_TOKEN } from '@servicetitan/shared-data-registry';
import { AtlasRegistryProvider, AtlasProvider } from '@servicetitan/assist-utils';

function App() {
    return (
        <Provider singletons={[{ provide: EVENT_BUS_TOKEN, useValue: new EventBus() }]}>
            {/* EventBus created by Host */}
            <AtlasRegistryProvider>
                {/* This registers the AtlasRegistry with the EventBus */}
                <AtlasProvider>
                    {/* Your host application code */}
                    <HostComponents />
                    {/* MFEs will be loaded here */}
                </AtlasProvider>
            </AtlasRegistryProvider>
        </Provider>
    );
}

Important:

  • Provider with EVENT_BUS_TOKEN - Creates the shared EventBus. Only the Host application creates this.
  • AtlasRegistryProvider - Registers the Atlas Registry with the EventBus. Only use this in the Host application.
  • AtlasProvider - Provides access to AtlasRegistryStore, AtlasRegistryReader, and AtlasRegistryWriter. Required for using hooks like useAtlasContextRegister and useAtlasHandlerRegistry.

2. MFE (Micro-Frontend) Setup

MFEs do NOT need AtlasRegistryProvider - they inherit the EventBus from the Host.

Wrap only the components that need registry access with AtlasProvider:

import { AtlasProvider } from '@servicetitan/assist-utils';

// Wrap specific components that use Atlas hooks
export const MfeCommunication = () => {
    return (
        <AtlasProvider>
            <MfeCommunicationComponent />
        </AtlasProvider>
    );
};

// Your MFE root doesn't need AtlasProvider if not all components use it
function App() {
    return (
        <div>
            <MfeCommunication />  {/* Only this uses AtlasProvider */}
            <OtherComponents />   {/* These don't need it */}
        </div>
    );
}

Note: You can wrap at the root level if all/most components need registry access, or wrap individual components for more granular control.

3. TI-Assist Setup

Same as MFEs - wrap components that need registry access with AtlasProvider:

import { AtlasProvider } from '@servicetitan/assist-utils';

export const AssistPanel = () => {
    return (
        <AtlasProvider>
            {/* Components using useAtlasToolInvoke, useAtlasDataConsumer, etc. */}
            <AssistComponents />
        </AtlasProvider>
    );
};

How It Works

Host Application
└── Provider (creates EventBus via EVENT_BUS_TOKEN)
    └── AtlasRegistryProvider (registers 'atlas-host-communication-registry')
        └── AtlasProvider (provides Store, Reader, Writer)
            ├── Host Components (can use hooks)
            ├── MFE1
            │   └── AtlasProvider (connects to same EventBus)
            │       └── MFE1 Components (can use hooks)
            ├── MFE2
            │   └── AtlasProvider (connects to same EventBus)
            │       └── MFE2 Components (can use hooks)
            └── TI-Assist
                └── AtlasProvider (connects to same EventBus)
                    └── Assist Components (can use hooks)

Key Points:

  • Host creates the EventBus via Provider with EVENT_BUS_TOKEN
  • AtlasRegistryProvider registers the Atlas Registry with ID: 'atlas-host-communication-registry'
  • All applications share the same EventBus through the shared registry ID
  • Only the Host uses Provider (for EventBus) and AtlasRegistryProvider (for registry)
  • All applications (Host, MFEs, TI-Assist) use AtlasProvider to access the registry
  • This ensures cross-MFE communication works seamlessly

Architecture Overview

System Components

┌─────────────────────────────────────────────────────────────────┐
│              Atlas Registry - (assist-utils)                    │
│  (Centralized message bus for cross-MFE communication)          │
└─────────────────────────────────────────────────────────────────┘
                              │
                ┌─────────────┴─────────────┐
                │                           │
        ┌───────▼──────┐            ┌──────▼─────┐
        │ Tool Registry│            │    Data    │
        │              │            │  Registry  │
        └───────┬──────┘            └──────┬─────┘
                │                           │
    ┌───────────┼───────────────────────────┼───────────┐
    │           │                           │           │
┌───▼────┐ ┌───▼────┐ ┌─────▼─────┐ ┌────▼─────┐ ┌───▼────┐
│  Host  │ │  MFE1  │ │   MFE2    │ │ TI-Assist│ │  MFE3  │
│  App   │ │        │ │           │ │          │ │        │
└────────┘ └────────┘ └───────────┘ └──────────┘ └────────┘

Communication Flow

  • Registration Phase: Each MFE registers its tools and data
  • Discovery Phase: Consumers discover available resources via the registry
  • Invocation Phase: Consumers invoke tools and subscribe to data
  • Update Phase: Changes propagate automatically through the registry

Core Concepts

1. Registry Pattern

The Atlas Registry uses a publish-subscribe pattern where:

  • Producers register resources (tools, data)
  • Consumers discover and use these resources
  • Registry maintains the connection and ensures data flow

2. React IoC (Inversion of Control)

All registry access uses dependency injection via @servicetitan/react-ioc:

const [reader] = useDependencies(AtlasRegistryReader);
const [writer] = useDependencies(AtlasRegistryWriter);

Why IoC?

  • Centralized state management across MFEs
  • Single source of truth for all registered resources
  • Automatic cleanup and memory management

3. Serialization Strategy

Serializable Data → Stored in Atlas Registry

This hybrid approach allows:

  • Metadata to be discoverable across all MFEs
  • Functions to be directly accessible without serialization issues

Atlas Registry System

AtlasRegistryWriter

Purpose: Add items to the registry

import { useDependencies } from '@servicetitan/react-ioc';
import { AtlasRegistryWriter } from '@servicetitan/assist-utils';

const [writer] = useDependencies(AtlasRegistryWriter);

// Add an item
const id = writer.add({
    type: 'MY_MESSAGE_TYPE',
    data: { foo: 'bar' }
});

// Remove an item
writer.remove(id);

AtlasRegistryReader

Purpose: Read items from the registry

import { useDependencies } from '@servicetitan/react-ioc';
import { AtlasRegistryReader } from '@servicetitan/assist-utils';

const [reader] = useDependencies(AtlasRegistryReader);

// Subscribe to registry updates
useEffect(() => {
    reader.subscribe(setItems);
    return () => reader.unsubscribe(setItems);
}, [reader]);

useRegistry Hook

Purpose: Combined reader/writer with message sending

import { useRegistry } from '@servicetitan/assist-utils';

const { items, sendMessage } = useRegistry();

// Send a message to all MFEs
sendMessage({
    type: 'USER_ACTION',
    data: { action: 'click', target: 'button' },
});

Tool Registry

Tools enable cross-MFE function execution - one MFE can invoke functions registered by another MFE.

Architecture

┌──────────────┐                    ┌──────────────┐
│   Provider   │                    │   Consumer   │
│   (Host)     │                    │ (TI-Assist)  │
└──────┬───────┘                    └──────┬───────┘
       │                                    │
       │ 1. Register Tool                   │
       │    useAtlasHandlerRegistry         │
       │                                    │
       ├────────────────────────────────────▶
       │  Metadata → Atlas Registry         │
       │  Handler                           │
       │                                    │
       │                            2. Discover Tools
       │                               useAtlasToolInvoke
       │                                    │
       │                            3. Invoke Tool
       │◀───────────────────────────────────┤
       │                                    │
       │ 4. Execute Handler                 │
       │                                    │
       │ 5. Return Result                   │
       ├────────────────────────────────────▶

useAtlasHandlerRegistry (Provider Side)

Purpose: Register a function that other MFEs can invoke

import { useAtlasHandlerRegistry } from '@servicetitan/assist-utils';

useAtlasHandlerRegistry('calculateTotal', {
    name: 'Calculate Total',
    description: 'Calculates the total price with tax',
    assistantId: 'assistant-123', // Optional: Associate tool with specific assistant
    parameters: [
        {
            name: 'price',
            type: 'number',
            description: 'Base price',
            required: true
        },
        {
            name: 'taxRate',
            type: 'number',
            description: 'Tax rate (0.0 to 1.0)',
            required: true
        },
    ],
    handler: ({ price, taxRate }) => {
        const tax = price * taxRate;
        const total = price + tax;
        return {
            subtotal: price,
            tax: parseFloat(tax.toFixed(2)),
            total: parseFloat(total.toFixed(2)),
        };
    },
});

How it works:

  • Metadata (name, description, parameters) → Stored in Atlas Registry
  • Handler function executes directly via the registry
  • Automatic cleanup on component unmount

useAtlasToolInvoke (Consumer Side)

Purpose: Discover and invoke tools from other MFEs

import { useAtlasToolInvoke } from '@servicetitan/assist-utils';

const { invokeTool, availableTools } = useAtlasToolInvoke();

// List all available tools
const tools = availableTools();
console.log(tools); // [{ toolKey: 'calculateTotal', name: 'Calculate Total', ... }]

// Invoke a tool
const result = await invokeTool('calculateTotal', {
    price: 100,
    taxRate: 0.15,
});
console.log(result); // { subtotal: 100, tax: 15, total: 115 }

How it works:

  • Subscribes to Atlas Registry for tool metadata
  • invokeTool() communicates with provider via the registry
  • Provider's handler executes and returns result via Promise
  • 5-second timeout for safety (configurable)

Tool Invocation Flow

// Step 1: Consumer invokes tool
invokeTool('calculateTotal', { price: 100, taxRate: 0.15 })

// Step 3: Provider handler executes
handler({ price: 100, taxRate: 0.15 }) // Returns result

// Step 5: Consumer receives result
// Promise resolves with { subtotal: 100, tax: 15, total: 115 }

useAtlasSystemMessage (MFE to TI-Assist Communication)

Purpose: Send system messages from MFEs to TI-Assist

System messages allow MFEs to provide context or trigger actions in the AI assistant without showing messages in the user-facing chat interface.

import { useAtlasSystemMessage } from '@servicetitan/assist-utils';

const MyMFEComponent = () => {
    const sendSystemMessage = useAtlasSystemMessage();

    const handleExport = () => {
        // Send context to TI-Assist about user action
        sendSystemMessage({
            text: 'User initiated data export',
            context: {
                action: 'export',
                selectedItems: [1, 2, 3],
                currentView: 'dashboard'
            },
            assistantId: 'assistant-123', // Optional
            interactionId: 'interaction-456' // Required for message creation
        });
    };

    return <button onClick={handleExport}>Export Data</button>;
};

How it works:

  • MFE calls sendSystemMessage() with payload
  • Message is sent via Atlas Registry with type ATLAS_SYSTEM_MESSAGE
  • TI-Assist's MessageCreatorStore listens for these messages
  • If interactionId is provided, a SystemMessage is created via API
  • The AI assistant receives the context and can respond accordingly

Parameters:

  • text (required): Description of the system event or context
  • context (optional): Additional structured data about the event
  • assistantId (optional): Specific assistant to target
  • interactionId (required): The interaction ID where the message should be created

Important Notes:

  • System messages are NOT displayed in the chat UI
  • They provide background context to the AI assistant
  • interactionId is required for the message to be created
  • Messages are automatically cleaned up from the registry after 2 seconds

Data Registry

Data Registry enables real-time data synchronization - changes in one MFE automatically reflect in all subscribed MFEs.

Architecture

┌──────────────┐                    ┌──────────────┐
│   Provider   │                    │   Consumer   │
│   (Host)     │                    │ (TI-Assist)  │
└──────┬───────┘                    └──────┬───────┘
       │                                    │
       │ 1. Register Data                   │
       │    useAtlasContextRegister         │
       │    { key, data }                   │
       │                                    │
       ├────────────────────────────────────▶
       │       Atlas Registry               │
       │                                    │
       │                            2. Subscribe
       │                          useAtlasDataConsumer
       │                                    │
       │ 3. Data Updates (state change)     │
       │                                    │
       ├────────────────────────────────────▶
       │    Auto-sync via Registry          │
       │                                    │
       │                         4. React re-renders
       │                            with new data

useAtlasContextRegister (Provider Side)

Purpose: Share live state data with other MFEs

import { useAtlasContextRegister } from '@servicetitan/assist-utils';

const [counter, setCounter] = useState(0);
const [status, setStatus] = useState('idle');

// Share live state - automatically updates consumers
useAtlasContextRegister({
    key: 'hostLiveState',
    description: 'Host application live state',
    assistantId: 'assistant-123', // Optional: Associate data with specific assistant
    payload: { counter, status },
});

Advanced: Memoization for Performance

const [counter, setCounter] = useState(0);
const [status, setStatus] = useState('idle');
const [otherData, setOtherData] = useState('...');

// Memoize to prevent unnecessary registry updates
const sharedData = useMemo(
    () => ({ counter, status }),
    [counter, status]
);

useAtlasContextRegister({
    key: 'hostLiveState',
    description: 'Host application live state',
    payload: sharedData, // Only updates when counter or status changes
});

How it works:

  • Registers data with unique key in Atlas Registry
  • Every time payload changes, registry is updated
  • All consumers automatically receive the update
  • Automatic cleanup on unmount

useAtlasDataConsumer (Consumer Side)

Purpose: Subscribe to live data from other MFEs

import { useAtlasDataConsumer } from '@servicetitan/assist-utils';

// Subscribe to data by key
const liveState = useAtlasDataConsumer<{ counter: number; status: string }>('hostLiveState');

// Use in component
return (
    <div>
        <p>Counter: {liveState?.counter}</p>
        <p>Status: {liveState?.status}</p>
    </div>
);

Type Safety:

interface HostState {
    counter: number;
    status: string;
}

const liveState = useAtlasDataConsumer<HostState>('hostLiveState');

// liveState is typed as HostState | undefined
if (liveState) {
    console.log(liveState.counter); // TypeScript knows this is a number
}

How it works:

  • Subscribes to Atlas Registry items
  • Filters for items with matching key
  • Returns the latest data
  • Automatically updates when provider changes data
  • Returns undefined if data doesn't exist yet

useAtlasDataList (Advanced)

Purpose: Get list of all available data sources

import { useAtlasDataList } from '@servicetitan/assist-utils';

const dataSources = useAtlasDataList();

console.log(dataSources);
// [
//   { key: 'hostLiveState', description: 'Host application live state' },
//   { key: 'mfe1UserInput', description: 'Live text input from MFE 1' }
// ]

Usage Examples

Example 1: Simple Tool Registration and Invocation

Host (Provider):

// packages/sandbox/src/components/tools/tools.tsx
import { useAtlasHandlerRegistry } from '@servicetitan/assist-utils';

const HostCommunication = () => {
    const [counter, setCounter] = useState(0);

    useAtlasHandlerRegistry('incrementCounter', {
        name: 'Increment Counter',
        description: 'Increments the counter in host',
        parameters: [
            { name: 'amount', type: 'number', description: 'Amount to increment', required: true }
        ],
        handler: ({ amount }) => {
            setCounter(prev => prev + amount);
            return { newCounter: counter + amount };
        },
    });

    return <div>Counter: {counter}</div>;
};

TI-Assist (Consumer):

// packages/ti-assist/src/modules/demo/demo.tsx
import { useAtlasToolInvoke } from '@servicetitan/assist-utils';

const Demo = () => {
    const { invokeTool, availableTools } = useAtlasToolInvoke();

    const handleIncrement = async () => {
        const result = await invokeTool('incrementCounter', { amount: 5 });
        console.log('New counter:', result.newCounter);
    };

    return (
        <div>
            <button onClick={handleIncrement}>Increment by 5</button>
            <p>Available tools: {availableTools().length}</p>
        </div>
    );
};

Example 2: Real-Time Data Sync

Host (Provider):

import { useAtlasContextRegister } from '@servicetitan/assist-utils';

const HostPanel = () => {
    const [userInput, setUserInput] = useState('');

    // Share user input in real-time
    useAtlasContextRegister({
        key: 'hostUserInput',
        description: 'Live text input from host',
        payload: userInput,
    });

    return (
        <input
            value={userInput}
            onChange={e => setUserInput(e.target.value)}
            placeholder="Type something..."
        />
    );
};

TI-Assist (Consumer):

import { useAtlasDataConsumer } from '@servicetitan/assist-utils';

const Demo = () => {
    const hostUserInput = useAtlasDataConsumer<string>('hostUserInput');

    return (
        <div>
            <h3>Host is typing:</h3>
            <p>{hostUserInput || '(No input yet)'}</p>
            <p>Characters: {hostUserInput?.length || 0}</p>
        </div>
    );
};

Advanced Patterns

Pattern 1: Tool Chaining

Invoke multiple tools in sequence:

const { invokeTool } = useAtlasToolInvoke();

const handleComplexOperation = async () => {
    // Step 1: Calculate total
    const totalResult = await invokeTool('calculateTotal', {
        price: 100,
        taxRate: 0.15
    });

    // Step 2: Update status with result
    await invokeTool('updateStatus', {
        status: `Total calculated: $${totalResult.total}`
    });

    // Step 3: Increment counter
    await invokeTool('incrementCounter', {
        amount: 1
    });
};

Pattern 2: Aggregated Data Display

Combine data from multiple sources:

const hostState = useAtlasDataConsumer<{ counter: number; status: string }>('hostLiveState');
const mfeState = useAtlasDataConsumer<{ counter: number; status: string }>('mfe1LiveState');

const totalCounter = (hostState?.counter || 0) + (mfeState?.counter || 0);

return (
    <div>
        <h3>Combined Counter: {totalCounter}</h3>
        <p>Host: {hostState?.counter || 0}</p>
        <p>MFE: {mfeState?.counter || 0}</p>
    </div>
);

Pattern 3: Tool Discovery and Documentation

Build a dynamic tool explorer:

const { availableTools } = useAtlasToolInvoke();

return (
    <div>
        <h2>Available Tools</h2>
        {availableTools().map(tool => (
            <div key={tool.toolKey} style={{ border: '1px solid #ccc', padding: '10px' }}>
                <h3>{tool.name}</h3>
                <p>{tool.description}</p>
                <h4>Parameters:</h4>
                <ul>
                    {tool.parameters.map(param => (
                        <li key={param.name}>
                            <strong>{param.name}</strong> ({param.type})
                            {param.required && ' *required*'}
                            <br />
                            <em>{param.description}</em>
                        </li>
                    ))}
                </ul>
            </div>
        ))}
    </div>
);

Best Practices

1. Tool Registration

✅ Do:

// Use descriptive tool keys
useAtlasHandlerRegistry('calculateTotalWithTax', { ... });

// Provide clear descriptions
description: 'Calculates the total price including tax based on the given tax rate'

// Validate parameters
handler: ({ price, taxRate }) => {
    if (typeof price !== 'number' || price < 0) {
        throw new Error('Price must be a positive number');
    }
    // ... rest of handler
}

❌ Don't:

// Vague tool keys
useAtlasHandlerRegistry('calc', { ... });

// Missing descriptions
description: ''

// No validation
handler: ({ price, taxRate }) => price + (price * taxRate)

2. Data Registry

✅ Do:

// Memoize data to prevent unnecessary updates
const sharedData = useMemo(
    () => ({ counter, status }),
    [counter, status]
);

useAtlasContextRegister({
    key: 'myData',
    payload: sharedData,
});

// Use specific keys
key: 'hostUserInputField'

❌ Don't:

// Don't pass raw objects (causes re-renders)
useAtlasContextRegister({
    key: 'myData',
    payload: { counter, status }, // New object every render!
});

// Don't use generic keys
key: 'data'

3. Error Handling

✅ Do:

// Wrap tool invocations in try-catch
try {
    const result = await invokeTool('calculateTotal', { price, taxRate });
    setResult(result);
} catch (error) {
    console.error('Tool invocation failed:', error);
    setError(error.message);
}

4. Performance

✅ Do:

// Use useMemo for expensive computations
const expensiveData = useMemo(() => {
    return computeExpensiveValue(input);
}, [input]);

useAtlasContextRegister({
    key: 'expensiveData',
    payload: expensiveData,
});

// Only re-register when necessary
useEffect(() => {
    // ... registration logic
}, [dataKey, writer]); // Minimal dependencies

5. Type Safety

✅ Do:

// Define interfaces for data
interface HostState {
    counter: number;
    status: string;
}

const hostState = useAtlasDataConsumer<HostState>('hostLiveState');

// Type tool parameters
interface CalculateTotalParams {
    price: number;
    taxRate: number;
}

handler: (params: CalculateTotalParams) => {
    // TypeScript knows params.price is a number
}

6. Naming Conventions

✅ Do:

// Tool keys: camelCase, descriptive verbs
'calculateTotal', 'updateUserProfile', 'fetchOrderDetails'

// Data keys: camelCase, source prefix
'hostUserInput', 'mfe1LiveState', 'dashboardMetrics'

7. Documentation

✅ Do:

useAtlasHandlerRegistry('calculateShippingCost', {
    name: 'Calculate Shipping Cost',
    description: 'Calculates shipping cost based on weight, distance, and shipping method. Returns cost in USD.',
    parameters: [
        {
            name: 'weight',
            type: 'number',
            description: 'Package weight in pounds',
            required: true
        },
        {
            name: 'distance',
            type: 'number',
            description: 'Shipping distance in miles',
            required: true
        },
        {
            name: 'method',
            type: 'string',
            description: 'Shipping method: "standard", "express", or "overnight"',
            required: true
        },
    ],
    handler: ({ weight, distance, method }) => {
        // Implementation
    },
});

Troubleshooting

Issue: Tools not showing up

Cause: Provider not properly configured or MFE not loaded

Solution:

  • Host Application: Ensure you have wrapped your app with both AtlasRegistryProvider and AtlasProvider
<AtlasRegistryProvider>
    <AtlasProvider>
        {/* Your app */}
    </AtlasProvider>
</AtlasRegistryProvider>
  • MFE/TI-Assist: Ensure you have wrapped your app with AtlasProvider
<AtlasProvider>
    {/* Your MFE code */}
</AtlasProvider>
  • Check that the MFE providing the tool is actually loaded and mounted

Issue: Data not updating

Cause: Data reference not changing (object identity)

Solution:

// Use useMemo to ensure reference changes only when data changes
const sharedData = useMemo(
    () => ({ counter, status }),
    [counter, status]
);

Issue: Infinite re-renders

Cause: Dependencies array causing effect to re-run continuously

Solution:

// Only include stable dependencies
useEffect(() => {
    // registration logic
}, [componentKey, writer]); // Don't include objects/arrays unless memoized

Issue: Tool invocation timeout

Cause: Provider not running or tool handler not registered

Solution:

  • Check console for [Atlas Tool Registry] Registered tool: toolName
  • Ensure provider MFE is loaded
  • Check tool key matches exactly (case-sensitive)

API Reference

Types

// Tool Configuration
interface FrontendToolParameter {
    name: string;
    type: string;
    description: string;
    required: boolean;
}

interface FrontendToolConfig {
    assistantId?: string; // Optional: Associate tool with specific assistant
    name: string;
    description: string;
    parameters: FrontendToolParameter[];
    handler: (params: any) => Promise<any> | any;
}

// Data Configuration
interface AtlasContextConfig<T = any> {
    assistantId?: string; // Optional: Associate data with specific assistant
    key: string;
    description?: string;
    payload: T;
}

License

Internal ServiceTitan package

FAQs

Package last updated on 01 Nov 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts