arrow-left

Only this pageAll pages
gitbookPowered by GitBook
1 of 30

CSML Reference

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Standard Library

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Installing CSML

As an open-source programming language, there are several ways you can get started with CSML, either by compiling the sources yourself, using Docker, or using CSML Studioarrow-up-right, a free online development environment provided by Clevy.ioarrow-up-right.

hashtag
No installation required: with CSML Playground

The easiest way to create CSML Chatbots is by using the free online development playground available at https://play.csml.devarrow-up-right. No dependencies, no installation, no signup required!

➡ Try CSML Playground now on !

You can also create a free account on CSML Studio, a professional online development and deployment tool for all your CSML chatbots. Read more , and head over to the !

CSML Studio includes everything you need to create, develop and maintain your chatbots. You get a code editor and test chatbox, built-in services to author functions and install custom or managed integrations, deploy to many different communication channels including Messenger, Workplace Chat, Microsoft Teams, Slack, Whatsapp... and monitor the usage with custom analytics.

➡ Try CSML Studio now on !

hashtag
Install CSML Locally

To run CSML on your own machine, you will need a way to run the engine (CSML comes with bindings for nodejs or directly in rust), as well as a database. Optionally, you will also need a custom App runtime ().

circle-info

explains how to run the CSML engine on your own server. It may be a useful starting point for a custom installation of CSML!

hashtag
With Docker

We provide an easy-to-use docker image to run CSML on your own computer. To get started, follow the instructions .

hashtag
Using a pre-built CSML Engine binary

We provide builds for macOS and linux (arm64) for every new version of the CSML engine. You can find them on the releases page of the . For the latest available version, visit .

hashtag
Compile From Source

CSML is written in Rust. You can find the source code of the CSML interpreter and engine on , and compile it from source as per the instructions.

hashtag

Introduction

hashtag
What is CSML?

CSML (Conversational Standard Meta Language) is an Open-Source, Domain-Specific Language designed for developing rich conversational experiences easily. It makes creating powerful chatbots extremely easy.

Written itself in Rust, the purpose of this language is to simplify the creation and maintenance of rich conversational interactions between humans and machines. With a very expressive and text-only syntax, CSML flows are easy to write and understand, making it easy to deploy and maintain conversational agents.

https://play.csml.devarrow-up-right
about CSML Studio herearrow-up-right
Studio documentationarrow-up-right
https://studio.csml.devarrow-up-right
more about this on this page
This blog postarrow-up-right
on Docker Hubarrow-up-right
CSML Engine github repositoryarrow-up-right
https://github.com/CSML-by-Clevy/csml-engine/releases/latestarrow-up-right
this github repositoryarrow-up-right
CSML natively handles short and long-term memory slots, metadata injection, and connecting to any third party API or injecting arbitrary code in any programming language thanks to its powerful runtime APIs.

By using the CSML language, any developer can integrate arbitrarily complex conversational agents on any channel (Facebook Messenger, Slack, Facebook Workplace, Microsoft Teams, custom webapp, ...) and make any bot available to any end user. In addition to the language itself, CSML Studioarrow-up-right, an online, full-featured development and deployment platform, comes with a large number of channel integrations that work out of the box, but developers are free to add new custom integrations by using the CSML interfaces.

hashtag
Getting started

This is a simple CSML flow example, which describes a conversation between a user and a machine.

A CSML flow contains several steps, which are defined by a name (which must be unique within a flow) followed by a colon. Each step contains instructions on how to handle the interactions within the step, what to say, what to remember, where to go. The syntax is very descriptive and contains very few keywords.

This goal of the example flow on the right is to retrieve the name of the user.

To do so, we will first check if we already have it in memory. If so, we can use it to greet our user, and close this conversation. Otherwise, we can go to a different step and simply ask for it. When the user responds, we will be able to remember the name for future use, and close the conversation.

This simple flow shows a few of the features of the language. There are many more, which we will go into in detail now!

/* Always start the conversation with this step */
start:
  /* The bot knows the name of the user */
  if (username) {
    say "Hi {{username}}!"
    say "It's nice to see you again."
    goto end
  }

  /* This is the first time we speak with the user */
  else {
    say "Hello World!"
    goto getname
  }


/* Step to retrieve the user's name */
getname:
  say OneOf(["What's your name?", "How do people call you?"])
  hold

  remember username = event
  say "OK, I now know that your name is {{event}}."
  goto end

Memory

Any good chatbot probably needs to be able to save and work with information gathered from the user at some point. The memory API provided in CSML is one of the most important parts of the language.

As you will see, some variables need to be stored in a long-term memory, while some variables are just temporary variables needed for local work. Some information is also provided by the context of the user and injected by the channel itself at the beginning of the conversation.

In this chapter, we will see how to save and use various types of memories depending on your scenario.

SMTP Client

CSML includes a native SMTP client, allowing you to easily send emails from your chatbot.

do email = {
 // required
 "from": "John <[email protected]>",
 "to": "Jane <[email protected]>",
 
 // either html or text is required, both can be set
 "html": "<h1>Hello in HTML!</h1>",
 "text": "Hi there in plain text",
 
 // optional
 "reply_to": "Iron Man <[email protected]>",
 "bcc": "James <[email protected]>",
 "cc": "[email protected]",
 "subject": "Happy new year",
}

do SMTP(hostname) // mandatory
 .auth(username, password) // optional, defaults to no auth
 .tls(true) // optional, defaults to auto upgrade to TLS if available
 .port(465) // optional, defaults to 465
 .send(email) // perform the send request

Using Variables in Messages

To output the value of any variable in a string, you can use the string interpolation helper: double curly braces {{ }}.

This makes it extremely easy to concatenate multiple values of any type into a single string. Objects, arrays and numbers will be stringified automatically and missing properties will be evaluated as NULL.

start:
  say "Hi! My name is {{what}}"
  say "My name is {{who}}"
  say "My name is {{slim_shady}}"

// Printing values from object properties is also possible:
start:
  say "Hi! My name is {{eminem.what}}"
  say "My name is {{eminem.who}}"
  say "My name is {{eminem.slim_shady}}"

// Alternatively, the following syntax is possible too, but will result in only one text bubble:
start:
  say "Hi! My name is {{what}}. My name is {{who}}. My name is {{slim_shady}}"

To concatenate two strings, you can also use the + operator (starting in CSML v1.8.0):

// the three lines below are equivalent
say firstname + " likes " + food
say "{{firstname}} likes {{food}}"
say [firstname, "likes", food].join(" ")

Automatic Type Inference

All incoming events are theoretically strings.

The reason is simple: there is no way for a chat client to differenciate between for example a number type and a string type as this was typed into an input field or spoken to a voice interface.

However, the language must have a way to perform arithmetic operations anyway.

At the same time, any external function used in the program can return any arbitrary JSON-like object representation, or string, or any type of value.

To handle all these cases at runtime, the CSML interpreter will automatically detect the type of any variable and try to make the best decision depending on how you want to use it. This process is called type inference.

The example below shows how it works for numbers.

Global variables

Inside any given step, a number of global variables are made available to the developer. These variables include past memories, step context, and request metadata. Some of these variables are available globally, including in CSML functions:

  • event: contains the user input, or NULL when empty. See The Event.

  • _metadata: read-only object that is injected into the conversation by the channel. Usage: _metadata.something

  • _env: read-only object containing the bot's defined environment variables. Usage: _env.something

  • _memory: read-only object containing the user's current memory state. This is especially helpful if you need to print the full state of the user's memory for debugging purposes!

hashtag
_metadata: user-level context

The _metadata global contains user-level context for each request.

This memory type is injected by the channel at the beginning of the conversation and contains any data that the channel already knows about the user that can be used in the conversation later. For example, this could include the user's full name or email address, their job title, where they are from...

hashtag
_context: holds flow positioning information

The _context can be found under _metadata and contains current_step, current_flow and default_flow

This memory type is injected and updated automaticaly at the start of each step

hashtag
_env: bot-level context

The _env global contains bot-level context for the entire bot and is shared across all users of the bot.

This memory type is injected by the bot itself and is meant to contain any data that the bot would need for common tasks across all users. For example, this is where you store the API keys, endpoints, feature flags...

Hash, HMAC

Encode data using hash and HMAC methods:

hashtag
Methods

hashtag
Crypto(data)

Conditional Logic

As in any programming language, CSML is able to handle any type of logic, in the form of loops and if/else statements.

As a CSML developer, you can easily implement any logic based on any variable or event, and very expressively decide how your conversation should be handled. Own of the main advantages of CSML is its descriptive textual interface, making it easy for anyone to understand the logic. It just makes sense and hides all the complexity of decision trees behind the simple concepts that every developer knows and uses.

A large part of developing CSML flows is about finding out whether an event matches a value in order to redirect the user to one step or another. The language comes with a number of helpers and operators:

  • comparison operators ==

/* event = "42" */

guessthenumber:
  say "pick a number!"
  hold

  /* compare the input with an arbitrary target number */
  if (event > 57) {
    say "Too high!"
    goto guessthenumber
  }
  else if (event < 57) {
    say "Too low!"
    goto guessthenumber
  }
  /* use the number as a string as well */
  else {
    say event
    say "You are right, {{event}} is the right number!"
    goto end
  }

Base64, Hex

You can easily encode and decode data to and from Base64 and Hex:

say Base64("Winter is coming 🥶").encode() // V2ludGVyIGlzIGNvbWluZyDwn6W2
say Base64("V2ludGVyIGlzIGNvbWluZyDwn6W2").decode() // Winter is coming 🥶

say Hex("Winter is coming 🥶").encode() // 57696e74657220697320636f6d696e6720f09fa5b6
say Hex("57696e74657220697320636f6d696e6720f09fa5b6").decode() // Winter is coming 🥶

This can for example be useful when using Basic Auth in HTTP calls:

do auth = Base64("user:password").encode()

do HTTP("https://example.com")
  .set({ "Authorization": "Basic {{auth}}" })
  .send()

But in general, they are useful methods to encode/decode non-URL-safe or non-ASCII data to ensure exchanges between systems run smoothly.

circle-info

Base64 and Hex are NOT encryption methods!

The data parameter is the data to encrypt or encode, and it must be a string.

hashtag
create_hash(algorithm)

The algorithm must be one of the following values:

hashtag
create_hmac(algorithm, key)

Unlike the create_hash method, create_hmac requires a key to function. The list of accepted algorithms is the same.

hashtag
digest(encoding)

The encoding must be either "base64" or "hex".

hashtag
Error handling

When any operation fails, Null is returned and a say Error(error_message) is emitted.

,
!=
,
<
,
>
,
<=
,
>=
  • match keyword

  • && and || operators

  • if, else if, else keywords

  • turing-completearrow-up-right
    do btn = Button("yes")
    do num = 1987
    do batman = "Bruce Wayne"
    
    if ("Hello!" match btn) {
      say "Not at all"
    }
    // for shorter code you can also use this shorthand version
    if (54 < num) say "Absolutely true!"
    
    if (3 >= 2 && batman != "Robin") say "Correct"
    
    if (username.match(batman)) say "I'm Batman"
    else if (username.to_lowercase() == "robin") say "I'm Robin"
    else {
      say "I'm apparently neither Batman nor Robin"
    }
    say "Hello {{_metadata.firstname}} {{_metadata.lastname}}!" // Hello Tony Stark!
    say _metadata._context.current_step
    
    goto @$_metadata._context.default_flow
    do HTTP("{{_env.COUNTRY_API_ENDPOINT}}/all")
      .set({ "Authorization": "Bearer {{_env.COUNTRY_API_TOKEN}}" })
      .get()
      .send()
    start:
      say Crypto("Hello World 😆").create_hash("sha256").digest("hex")
      say Crypto("Hello World 😆").create_hmac("md5", "SECRET_KEY").digest("base64")
    MD5
    SHA1
    SHA224
    SHA256
    SHA384
    SHA512
    SHA3_224
    SHA3_256
    SHA3_384
    SHA3_512
    SHAKE_128
    SHAKE_256
    RIPEMD160
    SM3

    Number methods

    Although CSML differentiates between Float and Integer, all methods are available on all Number types. Hence, the Number type does not really exist but represents indifferently Float and Integer.

    Given the automatic type inference, CSML will automatically try to map integers and floats to whichever type makes more sense in the context.

    hashtag
    .pow(Number)

    Raise the target integer to the power of the given integer.

    hashtag
    .abs()

    Return the absolute value of the target number.

    hashtag
    .cos(), .sin(), .tan()

    Return the cosine, sine or tangent of the target number.

    hashtag
    .floor(), .ceil(), .round()

    Round the number respectively down, up or using mathematical rounding.

    hashtag
    .precision(n)

    JWT

    The JWT module lets you encode, decode and verify Json Web Tokens easily!

    hashtag
    Methods

    hashtag
    sign

    exemple: JWT(data).sign(algorithm, secret)

    • data: json object to sign and convert to a JWT token

    • algorithm: see below

    • secret

    This method returns a properly encoded JWT as a string.

    hashtag
    decode

    exemple: JWT(token).decode(algorithm, secret)

    • token: the token to decode

    Note: decode does not try to verify that the token is valid. It simply decodes its payload back to the original form.

    This method returns the full JWT data, in the form:{ "payload": { ... your data }, "headers": { "alg": ALG, "typ": "JWT" }}

    hashtag
    verify

    exemple: JWT(token).verify(claims, algorithm, secret)

    • claims: set of claims (in JSON form) to verify the JWT against

    Note: only valid claims are verified. See the list of official claims in the JWT specs .

    This method returns the full JWT data, in the form:{ "payload": { ... your data }, "headers": { "alg": ALG, "typ": "JWT" }}

    hashtag
    Supported algorithms

    JWT supports the following algorithms: HS256, HS384, HS512 (corresponding to HMAC using SHA-256, SHA-384 and SHA-512 respectively).

    The secret key must be a valid, url-safe string.

    hashtag
    Error handling

    When a sign, decode or verify operation fails, Null is returned and a say Error(error_message) is emitted.

    Temporary and Long-Term variables

    hashtag
    Temporary Variables

    Local or temporary variables are only usable within the current step. It is rather a way to write more readable, expressive code. But they are really powerful, because they allow you to do everything that a regular memory does, but temporarily. For more information about local variables, see the as keyword.

    Local variables are useful for temporary calculations. You do not want to memorize your internal temporary steps, rather save useful information about the user.

    hashtag
    Long-Term Memory

    Long-term memories on the other hand are at the core of the purpose of CSML. When two persons converse, they constantly memorize information, which they can reuse in other discussions. For example, if I gathered that my friend likes Iron Man, I might propose later that we go see Captain America together. I do not have to ask them again about their favorite film, because I already learned this information before, even if it was in a different conversation, even if the topic of that conversation might have been completely unrelated.

    The memory API is very powerful: by default a bot never forgets anything about the current user. For more information, see the remember keyword.

    hashtag
    The Memory object

    CSML provides a _memory global, read-only variable, which contains all variables saved until now for the current user. This especially useful if you need to debug the state of a user's memory at any given step:

    hashtag
    ⚠️ Limitations

    circle-exclamation

    Variables and memories (of any type) can not be larger than 50KB. Using data larger than 50KB in variables (for example as the return value of a function) may result in undefined behavior. Also, please note that messages larger than 30KB in size can not be displayed in the test webapp.

    Generic methods

    The following methods are common to all CSML primitive types.

    hashtag
    .type_of()

    Return the type of the target value (string, array, object, float, boolean, int, null)

    hashtag
    .to_string()

    Return the target value formatted as a string.

    hashtag
    .is_number()

    Check if a variable can be interpreted as a number (float or int)

    hashtag
    .is_int()

    Check if the variable can be interpreted as an int

    hashtag
    .is_float()

    Check if the variable can be interpreted as an float

    hashtag
    .is_error(), .get_info()

    Some functions may resolve to an error due to a faulty user input, an unreachable API, or some other bug in the code. You can check the result of such functions call (like HTTP() or App()) with .is_error(), and get more information with .get_info().

    External Code Execution

    The CSML engine is able to automatically handle the execution of any payload, any code, in any language, thanks to the built-in App() macro. With CSML Apps, you can integrate your own business logic, on your own servers, in your language of choice.

    When used together with custom serverless function runtimes (cloud-based such as , , ), or on-premise with , or ), CSML Functions are a good way to execute custom business logic outside of the context of the conversation.

    circle-exclamation

    start:
      do payload = {
        "user": "name",
        "somekey": {
          "somevalue": 42
        },
        "exp": 1618064023,
        "iss": "CSML STUDIO"
      }
    
      do jwt = JWT(payload).sign("HS256", "SECRET_KEY")
      say jwt
    
      do decoded = JWT(jwt).decode("HS256", "SECRET_KEY")
      say "{{decoded}}"
    
      do claims = {
        "iss": "CSML STUDIO"
      }
      do verified = JWT(jwt).verify(claims, "HS256", "SECRET_KEY")
      say "{{verified}}"
    somestep:
      do tmpvar = "Hi there"
      say tmpvar // "Hi there"
      goto otherstep
      
    otherstep:
      say tmpvar // NULL
    : a secret string used to sign the JWT
    herearrow-up-right
    Deprecation notice: the original Fn() notation for calling Apps (formerly called Functions) in your CSML code has been replaced by the newer App() built-in as of CSML v1.5. Both notations will continue to work until CSML v2 is released, but this documentation will only reference the new App() usage from now on.

    hashtag
    Running App on CSML Studio

    When using the CSML Studioarrow-up-right, the heavy setup of creating an App runtime is already done for you, which means that you can easily import functions in java, python, nodejs, go... without any additional setup, simply by uploading your code and calling the function in your CSML script.

    CSML Studio also comes with many ready-to-use integrationsarrow-up-right that are installable in one-click.

    hashtag
    Executing App calls

    To execute external functions in any programming language when provided a fn_endpoint.

    For example, when a App is called, a HTTP POST request will be performed to the provided fn_endpoint with the following payload:

    The endpoint should return a JSON payload, formatted as follows:

    If the function fails, or returns an invalid payload, CSML will convert it as a Null value.

    hashtag
    Installing a custom nodejs App runtime

    The following repository provides an easy way to run your own nodejs runtime: https://github.com/CSML-by-Clevy/csml-fn-endpoint-nodearrow-up-right

    Follow the instructions to install and add your own apps!

    AWS Lambdaarrow-up-right
    Azure Functionsarrow-up-right
    Google Cloud Functionsarrow-up-right
    FnProjectarrow-up-right
    OpenFaasarrow-up-right
    OpenWhiskarrow-up-right
    number.pow(Number) => Number
    
    // example
    do val = 3
    do val.pow(2) // 9
    integer.abs() => Number
    
    // example
    do val = -42
    do val.abs() // 42
    float.cos() => Float
    float.sin() => Float
    
    // example
    do val = 12.34
    do val.cos() // 0,9744873988
    do val.sin() // -0,224442219
    do val.tan() // -0,2303182363
    float.floor() => Integer
    float.ceil() => Integer
    float.round() => Integer
    
    // example
    do val = 10.5
    do val.floor() // 10
    do val.ceil() // 11
    do val.round() // 11
    do val = 1.234567.precision(2) // 1.23
    somestep:
      remember tmpvar = "Hi there"
      say tmpvar // "Hi there"
      goto otherstep
      
    otherstep:
      say tmpvar // "Hi there"
    somestep:
      remember something = 1
      // ...
    
    someotherstep:
      do something = 8
      // ...
      
    wherever:
      // when we get there, is `something` set to 1, 8
      // or even available at all?
      say "{{_memory}}"
      
      
    any_value.type_of() => String
    any_value.to_string() => String
    string.is_number() => boolean
    
    // example
    do var_int = "42"
    say var_int.is_number() // true
    
    do var_float = "42.42"
    say var_int.is_number() // true
    
    do obj = {"toto": "tutu"}
    say obj.is_number() // false
    string.is_int() => boolean
    
    // example
    do var_int = "42"
    say var_int.is_number() // true
    
    do var_float = 42.42
    say var_int.is_number() // false
    string.is_float() => boolean
    
    // example
    do var_int = 42
    say var_int.is_number() // false
    
    do var_float = 42.42
    say var_int.is_number() // true
      do x = HTTP("https://somewhere.com/does-not-exist").send()
    
      if (x.is_error()) {
        // get info a description of the error
        debug "{{x.get_info()}}"
    
        // try something else or ask again the user for input!
      }
    findweather:
      say "Let me query the weather in {{location}} for you..."
      do weather = Fn("weatherchannel", location = location)
      say "It will be {{weather.temperature}}°C tomorrow."
    {
        "client": {
            "bot_id": "unique-bot-id",
            "user_id": "unique-user-id",
            "channel_id": "unique-channel-id"
        },
        "function_id": "name_of_fn",
        "data": {
            "example": "somevalue",
            "someparam": 123,
            "obj_val": { "hi": "there" }
        }
    }
    {
        "data": data_to_return<JSON>
    }

    Navigating in a CSML Bot

    hashtag
    Navigating within a flow

    A flow is made of at least one step (called start), but can also contain as many steps as necessary. You should think of steps as individual, simple bits of logic, linked inside the same conversation topic (which is the flow).

    To go from one step to the next, you can simply use the keyword goto (or goto step) followed by the name of the step.

    To finish a flow (and close the conversation), add goto end.

    circle-info

    If, at the end of a step, the goto end instruction is omitted, the conversation will be closed anyway.

    In other words,goto endcan be used to exit of a conversation early at any point, but it is implicitly added at the end of all steps if no other goto instruction is present.

    hashtag
    Navigating between flows

    Similarly to navigating between steps of the same flow, you can go to the beginning of any other flow by using the goto flow keyword. This will trigger the start step of the target flow and close the current flow, so coming back to the first flow will actually go back to the beginning of that flow.

    hashtag
    Advanced navigation

    Introduced in CSML v1.2

    If you want to reach a specific step in a specific flow, you can use the @ notation:

    This is the universal way of navigating between steps and flow. The above two methods are actually special cases of this notation:

    • When the @flow_name part is not specified, CSML interprets it as "in the current flow". So goto stepname actually is shorthand notation for goto stepname@current_flow, where current_flow is dynamically matched from the name of the current flow, and works accordingly.

    • When the step_name part is not specified, CSML interprets it as start

    hashtag
    Examples

    hashtag
    Dynamic step and flow names

    Introduced in CSML v1.5

    As you can see above, steps and flows use strings as identifiers, but they are expressed without double quotes "...". In order to navigate to dynamically set steps or flows, CSML adds a special syntax, similar to the dereference pointer concept in other languages.

    To go to a variable step or flow, you can reference the variable name prefixed with a $ sign:

    Any of the syntaxes presented above are supported. For instance:

    circle-exclamation

    Please note that dot notation (accessing child properties in an object or array, i.e a.b) is not supported in variable flow and step names.

    The Event

    The event keyword is a special variable in CSML that contains whatever the user did last. It can be generally be used as a simple string, but it is in fact a much more complex object that contains a lot of information about what the user did.

    In its essence, event contains the as received by the bot. There is a type and a content. The content is an object that contains a text, a payload, a url, other objects... it can be anything, and in general, its content can be derived from the events type. However, using it as "{{event}}" in a string (or simply in say event without any curly brace and double quote) will behave as a string.

    Message Payloads

    hashtag
    Standard message components

    CSML message components all have a matching message format for client use in regular JSON. They can be extended by adding additional properties to the content wrapper.

    HTTP Client

    CSML includes a native HTTP client. The following verbs are accepted:

    • GET

    • POST

    Crypto Utilities

    Available in CSML v1.5+

    . So as
    @
    means "flow",
    goto @flow_name
    is the same as
    goto start@flow_name
    which is also the same as
    goto flow flow_name
    .
    hashtag
    Event methods

    event contains a number of useful methods that can be used to perform various operations.

    hashtag
    event.match()

    In many cases we want to find whether the user's input matches some value to perform conditional logic. For example, in the following example, we want to make sure that the user clicked on the right answer:

    In some cases, you may also want to make sure that the user clicked on a button or typed any accepted response, in which case you can add several parameters to the .match() method:

    The result of event.match(...) is whatever was matched, or Null. So the above example, if using the language to its full potential, could also be used like this for even more control over what value is remembered:

    hashtag
    event.match_array()

    Similar to event.match(), but takes an array as argument. It also returns the matched value, if any. In many places, it is easier to use as listing all the individual items of the array. The same example as above would now read:

    hashtag
    event.get_type()

    Returns the type of event. For example, if the user typed something, event.get_type() will be "text". Or if the user clicked on a button (which yields a payload the type of event is "payload".

    hashtag
    Accessing event content values

    To access any value in the content body of the event, simply use the dot notation with its expected property name. If it does not exist, this will return Null.

    full message payload
    hashtag
    Text()

    hashtag
    Typing()

    hashtag
    Wait()

    hashtag
    Url()

    hashtag
    Image()

    hashtag
    Audio()

    hashtag
    Video()

    hashtag
    File()

    hashtag
    Button()

    hashtag
    Payload()

    hashtag
    Question()

    hashtag
    Child components

    Component payloads can be included into one another seamlessly. For example:

    PUT
  • PATCH

  • DELETE

  • HTTP will automatically add the following headers to any request:

    • Content-Type: application/json;

    • Accept: application/json

    circle-info

    In other words, HTTP requests only allow you to send and query json-formatted data.

    hashtag
    Performing a HTTP request

    To build your request, you can chain explicit methods to the HTTP function. No request is actually performed until .send() is added, allowing you to gradually build your request.

    Here is a simple example:

    You can also create more complex requests:

    The available methods are:

    • .get() / .post(body) / .put(body) / .patch(body) / .delete() : set the request verb, and add an optional JSON-formatted body

    • .set(data): set the request headers, where {"x-api-key":"somevalue"} is added as x-api-key:somevalue headers

    • .auth(username, password) set basic auth headers

    • .query(data): set the query strings, where {"key":"value"} is automatically translated to ?key=value

    • .disable_ssl_verify() : for situations where you know that the endpoint comes with an invalid SSL certificate, you can disable SSL verification to bypass this protection. This option should only be used if you know what you are doing!

    hashtag
    Analyzing the response

    You can check whether the call returned a response by using the .is_error() method on the response object.

    The .get_info() method lets you inspect the response of HTTP calls, even when the call returns an error (in which case .get_info() will also contain the body):

    .is_error() and .get_info() are very useful methods to correctly handle HTTP responses!

    start:
      say "hi"
      goto step otherstep // note: in the following examples, we will use the shorthand notation `goto otherstep`
    
    otherstep:
      say "I'm in otherstep"
      goto end
    somestep:
      goto flow anotherflow
    goto step_name@flow_name
    goto stepname // navigate to the step named stepname in the current flow
    goto flow flowname // navigate to the start step in flow flowname
    goto @flowname // navigate to the start step in flow flowname
    goto stepname@flowname // navigate to step stepname in flow flowname
    do mystep = "somestep"
    do myflow = "someflow"
    
    goto $mystep@$myflow
    do buttons = [Button("Burgers"), Button("Vegetables")]
    say Question(
      "What is your favorite food?",
      buttons = buttons
    )
    hold
    
    do matched = event.match_array(buttons)
    
    if (!matched) { /* handle case where input does not match anything */ }
    
    do target = matched.title
    goto flow $target
    
    // depending on which button the user selected, this is equivalent to
    // goto flow Burgers or goto flow Vegetables
    say Question(
      "What is 2+2?",
      buttons=[Button(4) as ok, Button(5) as notok]
    )
    hold
    
    if (event.match(ok)) say "That's correct!"
    else say "Wrong answer. The right answer was {{ok.title}}"
    say Question(
      "What is your favorite color",
      buttons=[
        Button("red") as btnred,
        Button("blue") as btnblue,
      ]
    )
    hold
    
    // this will be true if the user clicked
    // on one of the buttons, or typed "red" or "blue"
    if (event.match(btnred, btnblue)) remember favcolor = event
    else say "{{event}} is not a valid answer"
    
    say Question(
      "What is your favorite color",
      buttons=[
        Button("red", accepts=["Red", "Dark orange"], payload="COLOR_RED") as btnred,
        Button("blue", accepts=["Blue", "sky", "navy"], payload="COLOR_BLUE") as btnblue,
      ]
    )
    hold
    
    // for a valid choice, the user can either click on the button or 
    // manually type any of the accepted options for each button
    do matched = event.match(btnred, btnblue)
    
    // matched will contain the matched button object or Null
    
    // in case it contains the button object
    // we can prefer to remember its manually set payload
    // instead of the button's title
    if (matched) remember favcolor = matched.payload
    
    // or continue the same way as before
    else say "{{event}} is not a valid answer"
    say Question(
      "What is your favorite color",
      buttons=[
        Button("red", accepts=["Red", "Dark orange"]),
        Button("blue", accepts=["Blue", "sky", "navy"]),
      ] as buttons
    )
    hold
    
    // with match_array(), you don't have to list all options
    do matched = event.match_array(buttons)
    
    if (matched) remember favcolor = matched.title
    else say "{{event}} is not a valid answer"
    say "Type something below"
    hold
    say event.get_type() // "text"
    // Example event object:
    {
      "content_type": "text",
      "content": {
        // a valid text event will always have a content.text property
        "text": "Hi there", 
        // additional properties can be added
        "more_data": "some extra data",
        // extra properties of an event can be of any type.
        "some_number": 42,
        "an_object": { "wow": 1.23456, "impressive": true }
      }
    }
    
    say "text: {{event.text}}" // Hi there
    say "more_data: {{event.more_data}}" // some extra data
    say "doesnotexist: {{event.doesnotexist}}" // Null
    > Text()
    {
      "content": {
        "text": "message"
      },
      "content_type": "text"
    }
    > Typing()
    {
      "content": {
        "duration": 1000
      },
      "content_type": "typing"
    }
    > Wait()
    {
      "content": {
        "duration": 1000
      },
      "content_type": "wait"
    }
    > Url()
    {
      "content": {
        "url": "https://example.com",
        "title": "title",
        "text": "text"
      },
      "content_type": "url"
    }
    > Image()
    {
      "content": {
        "url": "https://example.com/image.jpg",
      },
      "content_type": "image"
    }
    > Audio()
    {
      "content": {
        "url": "https://example.com/audio.mp3",
      },
      "content_type": "audio"
    }
    > Video()
    {
      "content": {
        "url": "https://example.com/video.mp4",
      },
      "content_type": "video"
    }
    > File()
    {
      "content": {
        "url": "https://example.com/video.mp4",
      },
      "content_type": "file"
    }
    > Button()
    {
      "content": {
        "title": "Button title",
        "payload": "Button payload"
      },
      "content_type": "button"
    }
    > Payload()
    {
      "content": {
        "payload": "Custom payload"
      },
      "content_type": "payload"
    }
    > Question()
    {
      "content": {
        "title": "Question",
        "buttons": [Button]
      },
      "content_type": "question"
    }
    // CSML
    say Question(
        "Where is Brian",
        buttons = [
            Button("In the kitchen"),
            Button("Somewhere else"),
        ]
    )
    
    // JSON output
    {
        "content": {
        "title": "Where is Brian",
        "buttons": [
          {
            "content_type": "button",
            "content": {
              "title": "In the kitchen",
            }
          },
          {
            "content_type": "button",
            "content": {
              "title": "Somewhere else",
            }
          },
        ],
      },
      "content_type": "question"
    }
    do pet = HTTP("https://example.com/pets/1").get().send()
    
    // or, if no verb is specified, .get() is implicitly used
    do pet2 = HTTP("https://example.com/pets/2").send()
    
    say "This pet is called: {{pet.name}}"
    do req = HTTP("https://example.com/pets").set({"authorization":"Bearer XXXXX"})
    
    if (query_mode == "retrieve") {
        // retrieve all the available pets from the API
        do req = req.get()
    }
    else {
        // this will create a new pet
        do req = req.post({"name": "Oreo", "breed": "poodle"})
    }
    
    do res = req.send()
    
    say "{{res}}"
    do res = HTTP("https://example.com/does-not-exist").send()
    
    say res.is_error() // true
    do res = HTTP("https://example.com/does-not-exist").send()
    
    say "{{res.get_info().headers}}" // the response headers
    say "{{res.get_info().status}}" // 404
    say "{{res.get_info().body}}" // only set in case of error
    
    say "{{res}}" // the body in case of success, or Null in case of error
    // You can handle error cases like this:
    if (res.is_error()) {
      say "Hmmm... something happened"
    
      // For example, you can differentiate between 4XX and 5XX errors
      if (res.get_info().status >= 500) say "The server is not happy"
      else if (res.get_info().status >= 400) say "Something is wrong on the client side"
    }

    Value Types

    CSML is a dynamically-typed language, even though it is written in Rust, a strong-typed, memory-safe, low-level language. It can handle a lot of different types but some magic happens behind the scenes to make it as easy for the developers to use the language.

    In this section, let's learn more about how CSML handles the various quirks a chatbot needs to solve!

    hashtag
    Literals

    CSML is able to natively understand literal types (int, float, string, ...).

    integer and float are separate types, but most of the time you should not have to worry about it and consider it as a more generic and virtual number type.

    You can also express booleans with true or false .

    Since CSML v1.1, you can also use \n, \t, \r , \ and " characters in strings, with proper escaping (\ and " must be preceded by a \ while a single \ will be ignored). For example:

    hashtag
    NULL

    NULL is its own type. Missing values evaluate to NULL. The CSML interpreter will automatically parse the object with the usual dot notation x.y.z and detect the type of the resulting property. If one of the properties in the chain does not exist or is not an object itself, it will evaluate to NULL.

    hashtag
    Objects

    You can create an object by returning a JSON-like object from any function, or directly in the CSML code with the Object() helper function or by using a shorthand notation similar to JSON format.

    hashtag
    Arrays

    You can also iterate over an array (represented by items inside square brackets: ["a", "b", "c"]) with the foreach keyword, and access any of its items by using its index in square brackets notation: items[2].

    Note that foreach creates a copy of each item as it iterates over the array. So for example, in this next example, the array is not modified:

    You can modify the contents of an array either by assigning a new value to any of the items in the array, adding new items at the end with array.push(elem) or removing the last element with array.pop().

    You can also use to apply changes or get data from arrays.

    hashtag
    Printing non-literal values

    To print any value as a string, simply use the Text(value) component or use the curly-brace template syntax "{{value}}"

    Built-in Functions

    Macros are built-in functions that perform some common tasks. Below is a list of some common macros that you can use at any stage in your CSML flows.

    hashtag
    OneOf

    Return one the elements of the array passed in the first argument at random.

    Useful for randomisation of dialogue parts!

    hashtag
    Or

    If the first element exists return it, otherwise return the second argument as a default value

    hashtag
    Shuffle

    Given an array, return it with its elements in a random order.

    Especially useful in questions to randomize the available options!

    hashtag
    Find

    Return whether a string is contained in another string.

    hashtag
    Length

    Return the length of a given string or array

    hashtag
    Random

    Return a random floating point number in the range 0-1 (0 included, 1 excluded).

    hashtag
    Floor

    Return the largest integer less than or equal to the given number.

    This is useful to generate a random integer in a given range:

    hashtag
    UUID

    Generate a random UUID (v1 or v4, defaults to v4)

    hashtag
    Time

    The Time() helpers lets your manipulate timestamps and dates easily.

    circle-info

    CSML's Time function is based on Rust's Chrono library. All the formatting options are listed here:

    hashtag
    Exists

    Check if a variable has been saved in the chatbot's memory before for the current user.

    circle-exclamation

    Exists only checks for the existance of top-level variable names in the chatbot's memory for the current user, i.e obj and not obj.prop

    Object methods

    hashtag
    .keys()

    Return the list of all 1st-level properties in the target object.

    hashtag
    .values()

    do number = 3
    do string = "something"
    do float = -4.7
    do object = { "hey": "you" }
    do array = [1, 2, number]
    do boolean = true
    say OneOf(["I like you", "You're awesome"])
    Array methods
    https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.htmlarrow-up-right

    Return the list of all 1st-level-property values in the target object.

    hashtag
    .remove(String)

    Remove the given property from the target object.

    hashtag
    .length()

    Return the number of 1st-level properties in the target object.

    hashtag
    .assign(b)

    Insert all elements of the parameter object into the target object.

    object.keys() => Array[String]
    
    // example
    do val = { "toto": 1, "tutu": 2 }
    do val.keys() // ["toto", "tutu"]
    say "Hi!\n\nWe are proud to \ present this nice \"CSML v1.1\" update!"
    // Object representation
    do obj1 = Object(
      title = "toto",
      body = Object(
        somekey = "somevalue",
        otherkey = 123
      )
    )
    
    
    // Shorthand notation
    do obj2 = { 
      "title": "toto",
      "body": { 
        "somekey": "somevalue", 
        "otherkey": 123
      }
    }
    
    say obj1.title // "toto"
    say obj2.body.otherkey // 123
    do items = ["a", "b", "c"]
    
    // iterate over all the elements in the array
    foreach (elem, index) in items {
      say "index: {{index}}, elem: {{elem}}"
    }
    
    say items[2] // "c"
    do items = ["a", "b", "c"]
    
    foreach (item) in items {
      do item = item.to_uppercase() 
      say item // "A", "B", "C"
    }
    
    say "{{items}}" // ["a", "b", "c"]
    do items = ["a", "b", "c"]
    
    do items = ["a", "b", "c"]
    foreach (item, index) in items {
      do items[index] = item.to_uppercase() 
      say item // "a", "b", "c": the copy has not been affected
      say items[index] // "A", "B", "C": but the array content has been modified
    }
    
    say "{{items}}" // ["A", "B", "C"]: the array was modified
    say items[2] // "C"
    do items = ["a", "b", "c"]
    
    // print a stringified version of the array
    say "{{items}}"
    
    // alternatively, you can also use the .to_string() array method
    say items.to_string()
    say Or(var, "default value")
    do btn1 = Button("Blue")
    do btn2 = Button("Red")
    do btns = Shuffle([btn1, btn2])
    
    say Question(
      title = "Select a pill",
      buttons = btns
    )
    say Find("needle", in="haystack") // false
    say Find("yes", in="well yes, I like cheese") // true
    say Length("My horse is amazing") // 19
    say Length([1, 2, 3]) // 3
    say Random() // 0.03196249773128712
    say Random() // 0.6416423921015862
    say Floor(1.23456) // 1
    say Floor(Random() * 5) // a random integer between 0-4 (included)
    say Floor(Random() * 8) + 12 // a random integer between 12 - 19 (included)
    say UUID() // "aa4b9fb4-4d37-488c-981f-8aebc4eb9eaa"
    say UUID("v1") // "d0b40e8e-7ea4-11eb-9439-0242ac130002"
    say UUID("v4") // "4b784011-e49b-4913-9d58-7abf4f8a56bc"
    do myTime = Time() // initialize a Time object at the current UTC time
    do myTime = Time().at(2021, 03, 28, 12, 53, 20, 123) // initialize a time object at 2021-03-28T12:53:20.123Z
    
    do myTime = Time().unix() // generate the unix timestamp (in milliseconds)
    
    do myTime = Time().format() // returns an ISO8601 string
    do myTime = Time().format("%h%d") // returns a string with a custom format
    
    do myTime = Time().add(60) // adds 60 seconds to the value
    do myTime = Time().sub(60) // subtract 60 seconds to the value
    
    do myTime = Time().with_timezone("Europe/Paris") // set the time in the given timezone
    
    do myTime = Time().parse("2021-03-28") // parse a date
    do myTime = Time().parse("2021-03-28T12:53:20Z") // parse an ISO-formatted string
    do myTime = Time().parse("01/01/2021", "%d/%m/%Y") // parse a custom-formatted string
    do Exists("myvar") // false
    remember myvar = 123
    do Exists("myvar") // true
    object.values() => Array[String]
    
    // example
    do val = { "toto": 1, "tutu": 2 }
    do val.values() // [1, 2]
    object.remove("property") => void
    
    // example
    do val = { "toto": 1, "tutu": 2 }
    do val.remove("toto") // { "tutu": 2 }
    object.length() => Integer
    
    // example
    do val = { "toto": 1, "tutu": 2 }
    do val.length() // 2
    do obj = {"a": 1}
    do obj.assign({"b": 2})
    say "{{obj}}" // {"a": 1, "b": 2}

    Sending and Receiving Messages

    CSML is able to handle many types of messages by default.

    To send a message to the end user, simply use the keyword say followed by the message type you want to send.

    hashtag
    Styling text

    You can style text using Markdown. Some channels however only partially support Markdown (Messenger for example only allows bold, italic, strikethrough, but not tables), so don't get too fancy there.

    hashtag
    Quick Markdown reference

    hashtag
    Message types

    Below is a list of default valid message components, which are automatically converted to nicely formatted messages for the channel in which the user is talking with the bot.

    circle-info

    Components may receive additional optional parameters to accommodate the needs of certain channels.

    For example, in the Messenger channel you can add a button_type="quick_reply" parameter to the Question component to provide a different type of buttons.

    hashtag
    Asking Questions

    A conversation with a chatbot is not very different from a human-to-human dialogue: sometimes the user talks, sometimes the bot talks.

    CSML provides a solution when you need the chatbot wait for the user's input: using the hold keyword, the chatbot will remember its position in the conversation and simply wait until the user says something, then continue from there.

    hashtag
    Receiving messages

    When receiving an event from an end-user, the CSML interpreter will try to:

    1. match it with a new flow,

    2. or consume it in the existing conversation,

    3. or trigger the default flow as defined in the bot settings,

    by order of priority.

    CSML Events are made available in the script when a step is triggered by a user's input. The event object is a complex structure which contains a lot more than just the string that was typed by the user. It contains a lot of information that is used by the CSML interpreter, for example the custom button payload to match a list of choices offered to the user. This makes it easy to handle both a click on a "OK" button or the user typing the word "yes".

    By default, events are only expected when:

    • the flow was just triggered, the current step being triggered is start

    • the bot asked something to the user, and is waiting for the user's input at the same step

    In those cases, a local variable, event, is made available, with the content of the user request. When no event is available, event is set to NULL.

    hashtag
    ⚠️ Limitations

    triangle-exclamation

    The maximum theoretical size of a say payload is 16KB. However, each channel will have different limitations depending on the type of component. For a Text component for example, most channels are limited to a few hundred characters.

    Please refer to each channel's official documentation to find out the practical limitations of each component.

    start:
      say "I'm a text message"
      say Wait(1500)
      say Text("I'm also a text message")
      say Typing(1500)
      say Url("https://clevy.io")
      say Image("https://media.giphy.com/media/ASd0Ukj0y3qMM/source.gif")
      say Video("https://www.youtube.com/watch?v=DLzxrzFCyOs")
      say Audio("https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/253508261")
      say Question(
        title = "What is love?",
        buttons = [
          Button("baby don't hurt me"),
          Button("don't hurt me"),
          Button("no more")
        ]
      )
      say Carousel(
        cards = [
          Card(
            title="title",
            subtitle="an optional description",
            image_url="https://images.unsplash.com/photo-1567322329435-28658f2958c4",
            buttons=[Button("click me!")]
          )
        ]
      )

    Text(string)

    Image(string)

    Display the image available at URL string

    Text(string)

    Video(string)

    Display the video available at URL string. Supports Youtube, Dailymotion and Vimeo URLs, or mp4 and ogg.

    Url(string)

    Audio(string)

    Display the audio available at URL string. Supports Soundcloud embed links, or mp3, wav and ogg.

    Url(string)

    Button(string)

    Display string inside a clickable button

    Text(string*)

    Question(title = string(, buttons = [Button]))

    Display a list of buttons with a header of string. Title parameter is optional.

    Text(string) + list of buttons

    Card(title="string", buttons=[Button], image_url="string")

    Display nicely formatted content in a Carousel component (on supported channels). Only title is mandatory.

    Question(title, buttons) or Text(title)

    Carousel(cards=[Card])

    Display a list of Cardcomponents in a carousel (on supported channels). The cards parameter is mandatory.

    name

    description

    fallback

    Text(string)

    Output a simple string (with no styling attached). This is also the default type, alias of say "string". This component supports Markdown on channels that allow text formatting.

    Wait(number)

    Wait number milliseconds before sending the next message

    Typing(number)

    Display a typing indicator for number milliseconds

    Wait(number)

    Url(string, text="text", title="title")

    Format string as the URL. Optionally, the text and title parameters can provide a way to make "nicer" links, where supported.

    # Title 1
    ## Title 2
    
    **bold**
    *italic*
    ~strikethrough
    
    [link text](https://google.com)
    somestep:
      say Question("Do you like cheese?", buttons=[Button("yes"), Button("no")])
      
      hold
    
      if (event == "yes") say "I'm glad to know that you like cheese!"
      else say "Oh that's too bad!"

    String methods

    You can find more info about the particular regex syntax used in the *_regex methods on this linkarrow-up-right.

    hashtag
    .to_uppercase()

    Return the same string in all uppercase characters.

    hashtag
    .to_lowercase()

    Return the same string in all lowercase characters.

    hashtag
    .capitalize()

    Return the same string with the first letter in uppercase. The rest of the string remains unchanged.

    hashtag
    .trim(), .trim_left(), .trim_right()

    Returns a new string with both leading and trailing whitespace removed. .trim_left() and .trim_right() only trim the leading and trainling whitespace respectively.

    hashtag
    .length()

    Return the length of the target string.

    hashtag
    .contains(String), .contains_regex(String)

    Return whether the string contains another string or expression.

    hashtag
    .replace(), .replace_all(), .replace_regex()

    Replace the first, all or any occurrence matching the predicate:

    hashtag
    .starts_with(String), .starts_with_regex(String)

    Return whether a string starts with another string or expression.

    hashtag
    .ends_with(String), .ends_with_regex(String)

    Return whether a string ends with another string or expression.

    hashtag
    .match(String), .match_regex(String)

    Return all the matches of the string or expression in the target string, or Null if none are found.

    circle-info

    About _regex methods:

    The \ (backslash) character has a special meaning. For technical reasons, in all strings, it must be properly escaped, by convention by adding another \ in front of itself, to avoid being interpreted as a special character. For example, if you mean to write the exact string "\n" you must in fact write \\n, otherwise \n will be interpreted as a line break.

    hashtag
    .is_number(), .is_int(), .is_float()

    Return whether the given string represents a numerical value, an int, a float.

    hashtag
    .split(String)

    Split a string by a given separator and return an array containing all elements in order. The separator can be a single or multiple characters. If the separator can not be found in the string, the returned array will only contain the original string.

    hashtag
    .slice(start, end) => String

    Cut a string between the start and end characters. Some rules apply:

    • If end is not specified, all the characters after start are returned.

    • When specified, end must be ≥ start.

    hashtag
    .to_int(), .to_float() => Integer, Float

    Convert a string representing a number to a value cast as an integer or float:

    hashtag
    .to_yml(), .to_json()

    Convert yaml to json and back

    hashtag
    .encode_uri(), .encode_uri_component(), .decode_uri(), .decode_uri_component()

    Encode and decode URI/URIComponent (see )

    hashtag
    .encode_html_entities(), .decode_html_entities()

    Encode and decode

    Custom Components

    circle-exclamation

    Custom components are clearly an advanced feature of CSML. Unless you are missing specific features with the default capabilities of standard CSML message components, you probably will never need to learn more about this topic!

    Feel free to skip to the next section 😉

    hashtag
    Introduction to CSML Component templates

    Under the hood, all message components are created using a JSON-based templating system to describe how the component should behave, what properties it should accept, which are required or optional, their type, etc.

    For instance, here is the low-level code to generate the simple Text() component:

    Which outputs the following JSON payload when used in a CSML flow:

    Obviously, more complex components are also possible. Here is an example with the standard Button() component:

    You will notice the use of the $_get keyword. This function retrieves the value of the given parameter and sets if in place. So for example, in our example, the button's payload will be set to what the developers sets, or by default to the value of the title parameter. Same thing goes for the accepts property, which results in the following output:

    hashtag
    Creating Custom Message Components

    In addition to the , you can also add your own custom message components using the same templating format.

    hashtag
    params

    Each component is made of a list of params, that all have the same structure:

    hashtag
    required

    if set to true, will display an error when the value is not set in the flow

    hashtag
    type

    describe the type of the parameter to accept

    • Null

    • Bool

    • Number

    hashtag
    default_value

    default value to set on the object

    hashtag
    add_value

    values to add to the object in all cases (even if a default value is set)

    hashtag
    $_get, $_set

    • $_get: will inject the value of the given parameter

    • $_set: will set the field to a specific value

    hashtag
    Why Use Custom Components?

    In some cases, you may want to specialize your chatbot for one specific channel, which has some complex formats available that you want to make easier to use. For example, CSML Studio has a QuickReply custom component, which is a quicker way to create quick replies, which are features of Messenger, Workplace Chat and the Webapp channels.

    If you are making a specialized chatbot for a specific channel, consider using custom components to make your life easier!

    hashtag
    How to use Custom Message Components

    There are two ways to import your own custom components in the CSML Engine and use them in your flows.

    • as native components by saving your all your custom component files in files in a given directory, and set the COMPONENTS_DIR environment variable in your . In that case, the name of the component will be the name of the file (without the extension).

    • as local components by adding a list of custom components to your chatbot's configuration when calling the engine. In that case, you should add a custom_components parameter to your bot description with an array of key/value objects where the key is the name of the component and the value its definition.

    Native components and local components behave exactly as standard components. The only difference is that local components are used with a prefix (Component.MyComponent) in your flows, whereas native components can be called directly (MyComponent). This allows you to define different components per bot for the same engine instance, while being able to use common components across all your chatbots. Similarly, local components are easier to share with other users of your chatbots as they can be packaged together.

    Array methods

    hashtag
    .length()

    Return the number of elements contained in the target array.

    hashtag
    .push(*)

    string.to_uppercase() => String
    
    // example
    do val = "Where is Brian?"
    do val.to_uppercase() // "WHERE IS BRIAN?"
    This Python documentation explains why it especially matters in Regex syntax to escape backslashes: https://docs.python.org/2/howto/regex.html#the-backslash-plaguearrow-up-right

    We follow this nomenclature for CSML Regex handling, so a single Regex backslash must be written as a "\\" string, and an escaped backslash (that behaves as a literal "\" string character) must in fact be escaped twice, once for being in a string, and once for being in a Regex: you have to write "\\\\" to result in the Regex syntax \\which in turn matches the literal "\" string.

    In a future release of CSML we might introduce a "raw string" method to bypass this limitation.

    If any of the parameters is < 0, the count is made from the end of the string.
    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI#encodeuri_vs_encodeuricomponentarrow-up-right
    HTML entitiesarrow-up-right
    String
  • Array

  • Object

  • standard components
    CSML Enginearrow-up-right
    string.to_lowercase() => String
    
    // example
    do val = "Where is Brian?"
    do val.to_lowercase() // "where is brian?"
    string.capitalize() => String
    
    // example
    do val = "my name is John"
    do val.capitalize() // "My name is John"
    do text = "   Where is Brian?   "
    do new_text = text.trim()
    say new_text // "Where is Brian?"
    
    do new_text = text.trim_left()
    say new_text // "Where is Brian?   "
    
    do new_text = text.trim_right()
    say new_text // "   Where is Brian?"
    string.length() => Integer
    
    // example
    do val = "Where is Brian?"
    do val.length() // 15
    haystack.contains(needle) => Boolean
    haystack.contains_regex(needle) => Boolean
    
    // example
    do val = "Where is Brian?"
    // does it contain any "r"?
    do val.contains("r") // true
    // does it contain the word "where"?
    do val.contains("where") // false => no, because it is case sensitive
    // does it contain any number?
    do val.contains_regex("[0-9]") // true
    say "toto".replace("o", "a") // "tato" the first o char is replaced
    say "toto".replace_all("o", "a") // "tata" all o chars are replaced
    say "toto".replace_regex("[to]", "a") // "aaaa" all chars that are t or o are replaced
    haystack.starts_with(needle) => Boolean
    haystack.starts_with_regex(needle) => Boolean
    
    // example
    do val = "Where is Brian?"
    // does it start with "r"?
    do val.starts_with("r") // false
    // does it start with any uppercase letter?
    do val.starts_with_regex("[A-Z]") // true
    haystack.ends_with(needle) => Boolean
    haystack.ends_with_regex(needle) => Boolean
    
    // example
    do val = "Where is Brian?"
    // does it end with "r"?
    do val.ends_with("r") // false
    // does it end with any uppercase letter?
    do val.ends_with_regex("[A-Z]") // false
    haystack.match(needle) => Array[String]
    haystack.match_regex(needle) => Array[String]
    
    // example
    do val = "Where is Brian?"
    // does it match with "r"?
    do val.match("r") // ["r", "r"] => yes, twice!
    // does it match with any uppercase letter?
    do val.match_regex("[A-Z]") // ["W", "B"] => yes, and these are the letters!
    string.is_number() => Boolean
    
    // example
    do val = "Where is Brian?"
    do val.is_number() // false
    
    do val = "42"
    do val.is_number() // true
    do val.is_int() // true
    do val.is_float() // false
    string.split(String) => Array[String]
    
    // example
    do val = "this is a long string"
    do val.split(" ") // ["this", "is", "a", "long", "string"]
    do val.split("is") // ["th", " ", " a long string"]
    do val.split("camembert") // ["this is a long string"]
    say "abcdefghijklmnop".slice(2, 4) // "cd"
    say "abcdefghijklmnop".slice(7) // "hijklmnop"
    say "abcdefghijklmnop".slice(-4) // "mnop"
    say "abcdefghijklmnop".slice(-4, 14) // "mn"
    
    say "abcdefghijklmnop".slice(-4, 3) // Error
    say "abcdefghijklmnop".slice(2, 1) // Error
    do val = "1.2345".to_int() // 1
    do val = "1.2345".to_float() // 1.2345
    
    do val = "not a number".to_int() // error
    do json = "some:\n  yaml: 1".to_json()
    say "{{json}}" // {"some":{"yaml":1}}
    
    do yml = json.to_yaml()
    say "{{yml}}" // some:\n  yaml: 1
    say "https://mozilla.org/?x=шеллы".encode_uri()
    say "https://mozilla.org/?x=шеллы".encode_uri_component()
    
    say "https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B".decode_uri()
    say "https%3A%2F%2Fmozilla.org%2F%3Fx%3D%D1%88%D0%B5%D0%BB%D0%BB%D1%8B".decode_uri_component()
    debug "42 < 100".encode_html_entities()
    debug "42 &lt; 100".decode_html_entities()
    {
      "params": [
        {
          "text": {
            "required": true,
            "type": "String",
          }
        }
      ]
    }
    {
      "content_type": "text",
      "content": {
        "text": "Hello!"
      }
    }
    {
      "params": [
        {
          "title": {
            "required": true,
            "type": "String"
          }
        },
        {
          "payload": {
            "required": false,
            "type": "String",
            "default_value": [
              {"$_get": "title"}
            ]
          }
        },
        {
          "accepts": {
            "required": false,
            "type": "Array",
            "default_value": [
            ],
            "add_value": [
              {"$_get": "title" },
              {"$_get": "payload" }
            ]
          }
        }
      ]
    }
    // input: Button(title = "hi")
    
    // output:
    {
      "content_type": "button",
      "content": {
        "title": "hi",
        // retrieve the title by default
        "payload": "hi",
        // in this case, we are retrieving the value of `title` and the value
        // of `payload` which happens to be set to the value
        // of `title` by default and not overriden by any user value.
        "accepts": ["hi", "hi"]
      }
    }
    {
      "MyComponent": {
        "params": [
          {
            "title": {
              "required": true,
              "type": "String",
            }
          },
          {
            "info": {
              "required": false,
              "type": "String",
              "default_value": [
                {"$_set": "this is a default message" },
              ]
            }
          },
          {
            "list": {
              "required": false,
              "type": "Array",
              "add_value": [
                  {"$_get": "title" },
              ]
            }
          }
        ]
      }
    }
    {
      "params": [
        {
          "param_name": {
            "required": Boolean,
            "type": Type,
            "default_value": Array,
            "add_value": Array
          }
        }
      ]
    }
      "default_value": [
          {"$_get": "title"},
      ]
      "add_value": [
          {"$_set": "text" },
      ]
    
    say MessengerQuickReply(
        "here are some quick replies",
        buttons=["btn1", "btn2", "btn3"]
    )
    
    // same output, with the standard Question component
    say Question(
        "here are some quick replies",
        button_type="quick_reply",
        buttons=[Button("btn1"), Button("btn2"), Button("btn3")]
    )
    // as a native component
    start:
      say MyComponent("this is my custom component")
      goto end
    
    // as local component
    start:
      say Component.MyComponent("this is my custom component")
      goto end
    
    // JSON output
    {
      "content": {
        "title": "this is my custom component",
        "info": "this is a default message",
        "list": [
          "this is my custom component",
        ]
      },
      "content_type": "mycomponent"
    }

    Add an element at the end of an array.

    hashtag
    .pop()

    Remove the last element of an array.

    hashtag
    .reverse()

    Create a new Array with all elements reversed

    hashtag
    .append()

    Add the elements of the array to the initial array

    hashtag
    .insert_at(Integer, *)

    Add an element at position n of an array (shifting the position of all the following elements).

    hashtag
    .remove_at(Integer)

    Remove the nth element of an array (unshifting the position of all the following elements).

    hashtag
    .find(*)

    Returns a new array with all the values found in the original array matching the given value.

    You can find more info about the particular regex syntax used in the *_regex methods on this linkarrow-up-right.

    hashtag
    .to_uppercase()

    Return the same string in all uppercase characters.

    hashtag
    .to_lowercase()

    Return the same string in all lowercase characters.

    hashtag
    .length()

    Return the length of the target string.

    hashtag
    .contains(String), .contains_regex(String)

    Return whether the string contains another string or expression.

    hashtag
    .starts_with(String), .starts_with_regex(String)

    Return whether a string starts with another string or expression.

    hashtag
    .ends_with(String), .ends_with_regex(String)

    Return whether a string ends with another string or expression.

    hashtag
    .match(String), .match_regex(String)

    Return all the matches of the string or expression in the target string, or Null if none are found.

    circle-info

    About _regex methods:

    The \ (backslash) character has a special meaning. For technical reasons, in all strings, it must be properly escaped, by convention by adding another \ in front of itself, to avoid being interpreted as a special character. For example, if you mean to write the exact string "\n" you must in fact write \\n, otherwise \n will be interpreted as a line break.

    This Python documentation explains why it especially matters in Regex syntax to escape backslashes:

    We follow this nomenclature for CSML Regex handling, so a single Regex backslash must be written as a "\\" string, and an escaped backslash (that behaves as a literal "\" string character) must in fact be escaped twice, once for being in a string, and once for being in a Regex: you have to write "\\\\" to result in the Regex syntax \\which in turn matches the literal "\" string.

    In a future release of CSML we might introduce a "raw string" method to bypass this limitation.

    hashtag
    .init(size)

    Create a new array of size n

    hashtag
    .slice(start, end)

    Return a new array with all items between start and end. Some rules apply:

    • If end is not specified, all the items after start are returned.

    • When specified, end must be ≥ start.

    • If any of the parameters is < 0, the count is made from the end of the array.

    hashtag
    .map(fn), .filter(fn), .reduce(acc, fn)

    These methods are useful ways to construct a new arrays from existing arrays. They are inspired from similar methods in other languages and follow the same general syntax.

    Here are some examples of what these methods can help you achieve:

    hashtag
    .flatten()

    Convert an array of arrays to an array containing all elements of the 1st level arrays.

    Keywords

    hashtag
    Action keywords

    hashtag
    goto

    Inside a step, goto some other step. The step's memories are saved and its messages are sent right after this instruction, and before the next step is started.

    Special cases:

    • If the target step does not exist, the conversation will be closed.

    • If the target step is end, the conversation is closed.

    goto behaves as return in other languages: anything after a goto is ignored, the goto is immediately executed.

    To reach another flow, you can also use goto flow otherflow .

    hashtag
    say

    Send a message to the end user. For the full reference on Components

    hashtag
    debug

    Print a simplify version of the following value. Its output is a special debug component (same structure as a Text component with a debug content_type).

    If the value is a primitive (String, Number...), it will be printed entirely. If it is an Object or Array, only the structure of the first level will be printed.

    Unlike the say keyword, it can also be used inside native CSML functions:

    hashtag
    hold

    Wait for user input: simply hold the conversation in place until the user responds.

    hashtag
    hold_secure

    introduced in CSML v1.10.0

    Same as hold, except any user input that comes after that will not be saved in any CSML memory or displayable. However, you can perform operations on the value itself. This is a good use case for secret values that should not be saved in clear text anywhere in a database:

    circle-exclamation

    This keyword should be used with extreme care. It is for example a very bad practice to ask users for their password in a chat, and it should not be used for this purpose. However, this keyword can be extremely useful to share data (personal information, short-lived secrets...) that requires extra precautions or should not be stored in clear text (or at all).

    hashtag
    remember

    Save a value to the bot's memory with the given key. It can later be retrieved (as soon as the next step) with "{{memory_item}}".

    By default, the scope is bot/user/channel. The same user on a different channel, or a different user on the same channel, or the same user with a different bot will get a fresh memory instance.

    hashtag
    do

    Execute the following expression.

    Usually used for executing functions without caring for its return value, or for updating values of objects or arrays.

    When used in an assignment (as in x = y), the value x is saved as a local (temporary) variable.

    hashtag
    forget

    The forget keyword lets you forget memories selectively, or globally.

    hashtag
    Root-level keywords

    circle-info

    These keywords can only be used at the root of the flow, outside of any step

    hashtag
    const

    Declare constant values that are accessible and immutable across the whole flow. Two different flows can have a different set of const or different values for the same const!

    hashtag
    import

    see .

    hashtag
    Other keywords

    hashtag
    as

    Save any value as a local variable, only available within the step. Local variables remain in memory after the step is done.

    hashtag
    if / else if / else

    Simple logic operators if, else if and else. See examples.

    hashtag
    foreach

    Iterate over each element of an array.

    hashtag
    while

    A simple loop with a condition check at every turn

    hashtag
    break, continue

    Exit from loop early, or skip an iteration

    hashtag
    Deprecated keywords

    hashtag
    match (deprecated)

    circle-exclamation

    This syntax is obsolete, but for backwards-compatibility reasons remains valid CSML. However, prefer using the event.match(...) alternative which is much more versatile.

    Whether or not a variable "equals", in any way, another variable.

    hashtag
    use..as (deprecated)

    circle-exclamation

    This syntax is obsolete, but for backwards-compatibility reasons remains valid CSML.

    See as keyword.

    hashtag
    Operators

    hashtag
    Mathematical Operators

    In CSML, you can use the 4 basic mathematical operators +, -, * and /, as well as the modulo operator %. The regular order of operations applies.

    Additionally, since CSML v1.8.0, you can use the shortcut += and -= operators to add/subtract and assign values at the same time:

    hashtag
    String Concatenation

    Starting with CSML v1.8.0, you can concatenate two or more strings together by using the + sign:

    It is also possible to use string templating to insert any value in another string:

    array.length() => Integer
    
    // example
    do val = ["Batman", "Robin", "Superman"]
    do val.length() // 3
    array.push(val) => void
    
    // example
    do val = ["Batman", "Superman"]
    do val.push("Robin") // ["Batman", "Superman", "Robin"]
    array.pop() => void
    
    // example
    do val = ["Batman", "Robin", "Superman"]
    do val.pop() // ["Batman", "Robin"]
    do vec = [3, 2, 1]
    
    do new_vec = vec.reverse()
    say new_vec.to_string() // [1, 2, 3]
     do vec = [1, 2, 3]
     do vec2 = [4, 5, 6]
    
     do append_vec = vec.append(vec2)
     say append_vec.to_string() // [1, 2, 3, 4, 5, 6]
    array.insert_at(n, val) => void
    
    // example
    do val = ["Batman", "Superman"]
    do val.insert_at(1, "Robin") // ["Batman", "Robin", "Superman"]
    array.remove_at(n) => void
    
    // example
    do val = ["Batman", "Robin", "Superman"]
    do val.remove_at(1) // ["Batman", "Superman"]
    array.find(x) => Array
    
    // example
    do val = ["Batman", "Robin", "Superman", "Batman"]
    do val.find("Ironman") // []
    do val.find("Robin") // ["Robin"]
    do val.find("Batman") // ["Batman", "Batman"]
    string.to_uppercase() => String
    
    // example
    do val = "Where is Brian?"
    do val.to_uppercase() // "WHERE IS BRIAN?"
    string.to_lowercase() => String
    
    // example
    do val = "Where is Brian?"
    do val.to_lowercase() // "where is brian?"
    string.length() => Integer
    
    // example
    do val = "Where is Brian?"
    do val.length() // 15
    haystack.contains(needle) => Boolean
    haystack.contains_regex(needle) => Boolean
    
    // example
    do val = "Where is Brian?"
    // does it contain any "r"?
    do val.contains("r") // true
    // does it contain the word "where"?
    do val.contains("where") // false => no, because it is case sensitive
    // does it contain any number?
    do val.contains_regex("[0-9]") // true
    haystack.starts_with(needle) => Boolean
    haystack.starts_with_regex(needle) => Boolean
    
    // example
    do val = "Where is Brian?"
    // does it start with "r"?
    do val.starts_with("r") // false
    // does it start with any uppercase letter?
    do val.starts_with_regex("[A-Z]") // true
    haystack.ends_with(needle) => Boolean
    haystack.ends_with_regex(needle) => Boolean
    
    // example
    do val = "Where is Brian?"
    // does it end with "r"?
    do val.ends_with("r") // false
    // does it end with any uppercase letter?
    do val.ends_with_regex("[A-Z]") // false
    haystack.match(needle) => Array[String]
    haystack.match_regex(needle) => Array[String]
    
    // example
    do val = "Where is Brian?"
    // does it match with "r"?
    do val.match("r") // ["r", "r"] => yes, twice!
    // does it match with any uppercase letter?
    do val.match_regex("[A-Z]") // ["W", "B"] => yes, and these are the letters!
    do arr = [].init(3) // [null, null, null]
    do x = ["a", "b", "c", "d", "e"].slice(2, 4)
    say "{{x}}" // c, d
    
    do x = ["a", "b", "c", "d", "e"].slice(2)
    say "{{x}}" // c, d, e
    
    do x = ["a", "b", "c", "d", "e"].slice(-4)
    say "{{x}}" // b, c, d, e
    
    do x = ["a", "b", "c", "d", "e"].slice(-4, 3)
    say "{{x}}" // b, c
    
    do x = ["a", "b", "c", "d", "e"].slice(-2, 1)
    say "{{x}}" // Error
    
    do x = ["a", "b", "c", "d", "e"].slice(2, 1)
    say "{{x}}" // Error
    
    // create a new array where each original item is multiplied by 2
    do newArray = [1, 2, 3, 4].map((item, index) {
      return x * 2
    }) // newArray = [2, 4, 6, 8]
    
    // create a new array containing even numbers from the original array
    do newArray = [1, 2, 3, 4].filter((item, index) {
      return x % 2 == 0
    }) // newArray = [2, 4]
    
    // create a new value by adding all the elements together
    do sum = [1, 2, 3, 4].reduce(0, (acc, val, index) {
      do acc = acc + val
      return acc
    }) // sum = 1 + 2 + 3 + 4 = 10
    do [[1, 2], [3, 4]].flatten() // [1, 2, 3, 4]
    
    // if a 1st-level element is not an array, it will be kept as is
    do [[1, 2], "something", [3, 4]].flatten() // [1, 2, "something", 3, 4]
    https://docs.python.org/2/howto/regex.html#the-backslash-plaguearrow-up-right
    Native CSML Functions
    onestep:
      goto somestep
      say "after the goto" /* will not be executed */
      goto someotherstep /* will not be executed */
    
    somestep:
      say "hi"
    
    someotherstep: /* will not be executed */
      say "hey"
    say "The quick brown fox jumps over the lazy dog."
    do somevalue = 123
    debug somevalue // output: 123
    
    do mylargeobj = {
        "val": 1,
        "something": {"toto": "tutu" },
        "other": "hello",
        "onemore": [1, 2, 3]
    }
    debug mylargeobj // output: {"val": 1, "something": "[Object]", "other": "hello", "onemore": "[Array]"}
    fn is_triple_even(num):
      do triple = num * 3
      debug triple
      return (triple % 2)
    somestep:
      say "What's up doc?"
      hold
    
      remember updoc = event
      goto end
    start:
      say "Type something super secret, I promise I won't tell!"
      hold_secure // indicates that the next input will be secure
    
      // Value can not be displayed back or remembered in any way
      say "this is a secret: {{event}}" // prevented by CSML
      remember impossible = event // prevented by CSML
      do impossible = event // prevented by CSML
    
      
      // You can however perform operations on the event,
      // for example checking if the value is accepted
      if (event == "password") say "This is hardly a good secret!"
      else say "OK, good good"
    // remember a hardcoded value
    remember truc = "tutu"
    
    // remember a local variable
    do myvar = 123
    remember tata = myvar
    
    // overriding a local variable's scope
    do myvar = 123 // `myvar` has a local scope
    remember myvar = myvar // `myvar` is now a globally-available memory
    do myvar = myvar // `myvar` will still be available globally
    // execute a function
    do Fn("someFunc")
    
    // assign new values
    do array[3] = "X"
    do obj.val.toto = 123
    
    // however, this will fail:
    remember myarr = [1, 2]
    do myarr[42] = 1 // the array needs to have at least the requested number of items
    
    remember myobj = Object("key"="value")
    do myobj.missing.otherkey = 1 // all the parent properties must exist and be objects as well
    forget something // forget a single memory
    forget [something, otherthing, thirdthing] // forget several memories at once
    forget * // forget EVERYTHING (dangerous!)
    const MY_CONST = 42
    
    start:
      say "The value of MY_CONST is {{MY_CONST}}" // The value of MY_CONST is 42
      do MY_CONST = 14 // Forbidden! MY_CONST is immutable
    do Button("A") as btn1
    // is equivalent to
    do btn2 = Button("B")
    
    // say and save a component at the same time
    say Question(
      title = "Pick one",
      buttons = [btn1, btn2] as btnlist
    ) as myquestion
    
    // repeat the same question
    say myquestion
    // regular notation
    if (1 > 2) {
      say "I have my doubts"
    } else if ("mi casa" == "tu casa") {
      say "Welcome home"
    } else {
      say "The force is strong with you"
    }
    
    // shorthand notation
    if (sky == "blue") goto beach
    else goto restaurant
    do array = ["a", "b", "c"]
    foreach (val, index) in array {
      say "at position {{index}} is element with value {{val}}"
    }
    do i = 0
    while (i < 3) {
      say "i = {{i}}"
      do i = i + 1
    }
    remember lightsabers = [
      {"color": "red", "owner": "Kylo Ren"},
      {"color": "purple", "owner": "Mace Windu"},
      {"color": "yellow", "owner": "Rey Skywalker"},
      {"color": "green", "owner": "Yoda"},
      {"color": "red", "owner": "Darth Vader"},
      {"color": "green", "owner": "Luke Skywalker"},
     ]
    
    foreach (ls) in lightsabers {
      // we want to skip any red lightsaber
      if (ls.color == "red") continue
    
      say "{{ls.owner}} had a {{ls.color}} lightsaber"
      // we want to stop after we find the first green lightsaber
      if (ls.color == "green") break
    }
    
    say "There might be even more lightsabers!"
    do Button(
      title = "I agree",
      accept = ["OK", "yes", "right"]
    ) as btn
    
    /* a direct click on the button will match the button */
    /* typing "yes" will match the button */
    /* typing "of course" will not match the button */
    if (event match btn) { // Note: this is equivalent to event.match(btn)
      say "good"
    } else {
      say "not good"
    }
    use 42 as answer
    
    say "The answer is {{answer}}"
    do i = 8
    do i += 5
    say "i = {{i}}" // i = 13
    do val1 = "John"
    do val2 = "Nutella"
    say val1 + " likes " + val2 // John likes Nutella
    do val1 = "John"
    do val2 = "Nutella"
    say "{{val1}} likes {{val2}}" // John likes Nutella

    Native CSML Functions

    hashtag
    Native CSML Functions

    CSML functions are a simple way to define simple functional code that can be reused across all of your flows. They are defined in any flow file and use the same CSML syntax that is available in any other steps.

    Conceptually, they can be viewed as simple placeholders that you can insert at any place in any flow, to perform repetitive tasks.

    Declaring a function is similar to declaring a step with some minor differences:

    • the name of the function is preceded by the keyword fn ,

    • there is a list of parameters between parentheses after the name of the function,

    • but also, like a step declaration, it ends with a semicolon : or is surrounded by braces {...}

    Here is an example of a simple my_double_add() function, that takes 2 parameters, multiplies each of them by 2, then adds those numbers together, and returns the result:

    There is no limit on how many functions a flow can have, and of course you can call a function from within another function. The above example could be rewritten as:

    hashtag
    Limitations

    CSML native functions are isolated from the bot itself and can't interact with the memory or display messages. They have only access to non-state-binding keywords: do, if, foreach and return.

    However, other builtins can be used!

    hashtag
    Importing Functions From Other Flows

    Functions are by default attached to the flow where they are written. However, you can import functions from other flows to reuse them elsewhere!

    You can also use a global import by not defining the flow from which a function can be imported.

    circle-exclamation

    Warning! If there is more than one flow defining a function with the same name, it will just import one of them randomly.

    Don't use the same name on several functions, or specify which flow you want to import from

    hashtag
    Advanced usage

    You can import multiple functions at once, and even rename them:

    hashtag
    Modules

    introduced in CSML v1.10.0

    CSML Modules are an easy way to reuse CSML Functions across multiple chatbots or to provide access to your own services to other chatbot developers. You can create functions in separate flows that you can host on a repository to ease access.

    A module at its core is simply a valid CSML flow, that exposes one or several functions, which can be imported into other flows without needing to copy the flow multiple times in multiple bots. An other benefit of using modules is that it makes it easier to replicate changes across multiple changes.

    hashtag
    Usage

    To use a module, you must first declare it as a dependancy to your chatbot. when creating a new version of a chatbot or chatting with an unversioned chatbot.

    Some example modules are given in this repository:

    hashtag
    Example

    Let's import the buttons module into a flow to access the YesNoButtons function and make it easier to display Yes/No types of buttons.

    The module specification is as follows:

    name: buttons url: auth: none

    Then you can simply import it in your CSML script as you would any other function, the only difference being the modules/ prefix that tells CSML that it should look for this function in the buttons module and not in the bot's other flows.

    (new syntax introduced in v1.10.0)
    This is done in the modules section of the bodyarrow-up-right
    https://github.com/CSML-by-Clevy/csml-modulesarrow-up-right
    https://raw.githubusercontent.com/CSML-by-Clevy/csml-modules/master/modules/buttons.csmlarrow-up-right
    // v1.10.0+ syntax
    fn my_function_name(param1, param2, param3, ...) {
        /** do something **/
        return some_val
    }
    
    // old syntax, but still supported in newer versions!
    fn my_function_name(param1, param2, param3, ...):
        /** do something **/
        return some_val
    fn my_double_add(x, y) {
      do dbl_x = x * 2
      do dbl_y = y * 2
      return dbl_x + dbl_y
    }
    
    start:
      do value = my_double_add(1, 2)
      say "The result is: {{value}}" // 6
      goto end
    fn my_double(num) {
      return num * 2
    }
    
    fn my_add(a, b) {
      return a + b
    }
    
    fn my_double_add(x, y) {
      do dbl_x = my_double(x)
      do dbl_y = my_double(y)
      return my_add(dbl_x, dbl_y)
    }
    // Forbidden
    fn my_func(a) {
        say "hello"
        remember something = 42
        hold
        goto somestep
    }
    // Accepted
    fn my_func(a) {
        do x = 0
        foreach (item) in a {
            do x = x + item.value
            if (x > 42) break
        }
        do HTTP("https://myapi.com/").post().send()
        return x
    }
    // import this function specifically from this flow
    import my_function from flow_2
    
    start:
      do value = my_function(1, 2)
      say value
      goto end
    // import a function with this name from any other flow
    import my_function
    // import several functions at once
    import {my_function, other_function} from flow_2
    
    // you can rename a function
    import my_function as better_name from flow_2
    
    start:
      do value = better_name(42)
      say "The result is {{value}}"
    import YesNoButtons from modules/buttons
    
    start:
      say Question("Do you like strawberries?", buttons=YesNotButtons("Yes, of course", "Not really"))
      hold
      say "Your answer was: {{event}}"