Adding Custom Functions#

Extend FlowKit with your own functions by following three simple requirements.

Function Requirements#

1. Add @displayName Tag#

Include a @displayName tag in comments so your function appears in the UI:

// ReverseString reverses the input string
//
// Tags:
//   - @displayName: Reverse String
//
// Parameters:
//   - input: The string to reverse
//
// Returns:
//   - reversed: The reversed string
func ReverseString(input string) (reversed string) {
    // Implementation
}

2. Name All Parameters and Returns#

FlowKit requires named parameters and return values:

// Good - named parameters and returns
func ProcessData(input string, format string) (result string, success bool)

// Bad - unnamed returns
func ProcessData(input string, format string) (string, bool)

3. Use Panic for Errors#

Handle errors by panicking with clear messages:

func ProcessData(input string) (result string) {
    if input == "" {
        panic("input cannot be empty")
    }
    // Process data...
    return result
}

Step-by-Step Guide#

Step 1: Add Your Function

Add to an existing category file in pkg/externalfunctions/ (for example, generic.go):

func YourFunction(param1 string, param2 string) (result string) {
    // Your implementation
    return result
}

Step 2: Register Your Function

Add to ExternalFunctionsMap in pkg/externalfunctions/externalfunctions.go:

var ExternalFunctionsMap = map[string]interface{}{
    // ... existing functions ...
    "YourFunction": YourFunction,
}

Step 3: Build and Test

# Generate required files
go generate ./pkg/externalfunctions

# Build
go build ./...

# Run and test
go run main.go

Adding New Categories#

To create a new function category:

  1. Create pkg/externalfunctions/yourcategory.go

  2. Add embed directive in main.go:

    //go:embed pkg/externalfunctions/yourcategory.go
    var yourCategoryFile string
    
  3. Register in the files map:

    files := map[string]string{
        // ... existing categories ...
        "your_category": yourCategoryFile,
    }
    

Adding Custom Types#

For complex data structures, define types in pkg/externalfunctions/types.go:

type MyCustomType struct {
    Field1 string `json:"field1"`
    Field2 int    `json:"field2"`
}

Use JSON strings to pass complex types:

func ProcessCustomType(data string) (result string) {
    var custom MyCustomType
    if err := json.Unmarshal([]byte(data), &custom); err != nil {
        panic(fmt.Sprintf("Invalid JSON: %v", err))
    }
    // Process...
    output, _ := json.Marshal(custom)
    return string(output)
}

Important

Custom Types Registration

If you need custom input or output structs, they must be registered in the aali-sharedtypes repository:

  1. Add your type definition to aali-sharedtypes with proper JSON tags

  2. Implement type converters in aali-sharedtypes/pkg/typeconverters/typeconverters.go:

    • ConvertStringToGivenType - converts string to your custom type

    • ConvertGivenTypeToString - converts your custom type to string

  3. Update the UI constants in aali-agent-configurator if the type should be available in the UI

After registering your custom types:

  1. Merge your changes to the main branch in both aali-sharedtypes and aali-agent repositories

  2. Import the newest version of aali-sharedtypes in both FlowKit and the Agent

  3. The AALI Agent handles all type conversion and keeps track of variable values throughout workflow execution

  4. If a new variable type is not imported in the Agent, it cannot handle it properly

The type converters are actually called by the Agent (not FlowKit directly) to convert between string representations and actual Go types. This is why both FlowKit and the Agent must have the updated shared-types imported.

Without proper registration in aali-sharedtypes, your custom types will not work correctly with the FlowKit type conversion system.

Tips#

  • All inputs and outputs must be strings

  • Use JSON for complex data structures

  • Check existing functions for patterns

  • Test with grpcurl or client code