Stepsailor
  • 👋Welcome
  • Command Bar
    • Getting Started
  • Connecting your logic
  • CMD Bar Search
  • Customisation
  • Platform
    • Knowledge Bases
    • Orion Config
    • Deploy Config
  • Understanding Credits
  • Data jobs
  • Integrating Orion
  • Other
    • FAQ
Powered by GitBook
On this page
  • Understanding the main components of the SDK
  • Input Schema
  • Preview handler (the human in the loop)
  • Handler (the action itself)

Connecting your logic

PreviousGetting StartedNextCMD Bar Search

Last updated 2 months ago

Understanding the main components of the SDK

Big picture example (good overview)

This code annotates the key components and explains their purpose. This code is written for Next.js but looks very similar all frameworks or libraries.

It gives a good overview, but there are smaller more digestable exampels following up.

Stepsailor.tsx
'use client';

import { useEffect } from 'react';
import { CommandBarApi, defineSchema, fillableByAI, z } from '@stepsailor/sdk';

export default function StepsailorSdk() {
  // Here you would load all the dependencies to contexts (e.g. your session)
  //const session = getSession();
  
  // Making sure it runs on mount and not to often
  useEffect(() => {
    setupSdk();
  }, []);

  return null;
}

// in here you define everything that command should know.
function setupSdk(/* in here you can define context that should be avaialbe */) {
  const cmdBar = new CommandBarApi<{user: User}>();
  
  // defineContext make sure every handler can access the user object,
  // settings or more.
  cmdBar.defineContext({user});

  // An action is something that the AI can schedule based on the user input.
  // Actions have an input schema which is partly visible to the AI agent
  // You could imagine this like a tool call in agentic systems (but its not)
  cmdBar.defineAction({
    name: 'createAFolder', // <- camelCase name for AI Agent
    description: 'Creates a folder with a given name', // <- description for AI Agent
    displayName: 'Create a folder', // <- The display name for the user
    
    // The input schema defines a typesafe runtime-typing. 
    inputSchema: defineSchema({
      name: fillableByAI(z.string()), // <- AI agent will be able to fill this field
      someParentFolder: z.string().optional() // <- not visible to the AI agent
    }),
    
    // The preview handler gives you the chance to validate the data 
    // the AI agent filled in and ask the user for other remaining input.
    // This handler enables you to add human in the loop workflows in your cmdBar
    previewHandler: async ({ name, someParentFolder }, context) => {
      const { askForInput } = context.hil; // <- easiest way to get user input.
      
      // code to ask for input (explained in further examples)
      // code to display the selected input to make sure user is fine 
      // with AIs choices.
      return {name, someParentFolder};
    }, 
    
    // The core of the action is the handler. This where you define
    // what the action actually does
    handler: async (input, context) => {
      const { display } = context.hil;
      
      // Informing the user about what the action is doing
      display.log(`Creating folder with name ${input.name}`);      
      
      // You can run your server actions or api calls directly in here
      // and reuse logic that you have already used in your UI.
      const results = await serverActionForCreatingFolder({ input.name });
    },
  });

  // This makes your defined actions available for the command bar
  cmdBar.activate();
}

Input Schema

The schema makes the handler functions type-safe and enables you to define what the AI agent is allowed to fill in and what not. Giving you full control to reduce halluzinations. You can define multiple types

cmdBar.defineAction({
      // […] Other config
      inputSchema: z.object({
            websiteLinks: fillableByAI(z.array(z.string())),
            dataStoreId: z.string(),
            numberOfDataPointsToCreate: fillableByAI(z.number())
      })
})

Preview handler (the human in the loop)

The preview handlers run immediately after CmdBar AI came up with an action flow. Its the place where you can make sure the input for the handler is valid and the user is fine with the selected data.

 cmdBar.defineAction({
    name: 'crawlData',
    description: 'Crawls websites inorder to create reference data and stores it in a data store',
    displayName: 'Crawl websites',
    inputSchema: z.object({
      websiteLinks: fillableByAI(z.array(z.string())),
      dataStoreId: z.string(),
    }),
    previewHandler: async ({ websiteLinks, dataStoreId }, { hil }) => {
      // dataStoreId will be undefined since AI can't know that.
      
      const links = [];
      
      // main human in'the loop interfaces...
      const { askForInput, display } = hil;
      
      // validating input...
      if (websiteLinks && websiteLinks.length > 0) {
        links.push(...websiteLinks);
      } else {
        const link = await askForInput.text({
          label: 'Provide a link to crawl',
          placeholder: 'https://www.doc.com/abc',
        });
        links.push(link);
      }
      
      // requesting dynamic / critical input
      const dataStores = await getAvailableDataStores();
      const _dataStoreId = await askForInput.enum({
        label: 'Where should the data be stored?',
        options: new Map(dataStores.map((ds) => [ds.id, ds.name])),
      });

      return { dataStoreId: _dataStoreId, websiteLinks: links };
    },
    handler: async (validatedInput, context) => {},
  });

Handler (the action itself)

The handler runs for each action after the user hits "execute". It has the same capabilities and api as the previewHandler but its supposed used for executing the actual logic and only ask for input in edge cases.

 cmdBar.defineAction({
    name: 'crawlData',
    description: 'Crawls websites inorder to create reference data and stores it in a data store',
    displayName: 'Crawl websites',
    inputSchema: z.object({
      websiteLinks: fillableByAI(z.array(z.string())),
      dataStoreId: z.string(),
    }),
    // [...] - preview handler
    handler: async (validatedInput, context) => {
      const { display } = context.hil;

      display.log('The action is running');
      
      await new Promise((resolve) => setTimeout(resolve, 1000));
      
      display.log('And this log will stay and not replace the other');
      
      await new Promise((resolve) => setTimeout(resolve, 1000));
      
      const liveLog = display.syncedLog();
      liveLog.log('...');
      liveLog.log('currently its working on crawling...');
      
      await new Promise((resolve) => setTimeout(resolve, 4000));
      liveLog.log('crawling done');
      
      await new Promise((resolve) => setTimeout(resolve, 6000));
      liveLog.log('live logging updates the log in real time');
      
      await new Promise((resolve) => setTimeout(resolve, 3000));
      
      // closing the livelog connection
      liveLog.close();
      await new Promise((resolve) => setTimeout(resolve, 1000));
      liveLog.hide();

      display.log('The action is done and the live log is hidden');
    },
  });

The input schema is based on , because it is one of the most powerful static type inference toolings.

Zod
Click to magnify...
This how executing an action looks like when logging is used