import { z } from 'zod' const VALIDATION_RULES = ['text', 'number', 'date', 'regex', 'type'] as const export const CONTENT_TYPES = [ 'text', 'select-immediate', 'select', 'media', 'media-audio', 'media-video', 'media-document', 'chat-state', 'web-link', 'raw-content', '', ] as const export const MIME_TYPES = [ 'text/plain', 'application/vnd.lime.media-link+json', 'application/vnd.lime.select+json', 'application/vnd.lime.chatstate+json', 'application/vnd.lime.web-link+json', ] as const const HTTP_METHODS = ['POST', 'GET', 'PUT', 'DELETE'] as const const COMPARISON_OPERATORS = [ 'equals', 'notEquals', 'contains', 'startsWith', 'endsWith', 'greaterThan', 'lessThan', 'greaterThanOrEquals', 'lessThanOrEquals', 'matches', 'approximateTo', 'exists', 'notExists', ] as const const SELECT_SCOPES = ['immediate', 'persistent'] as const const LINK_TARGETS = ['blank', 'self', 'selfCompact', 'selfTall'] as const const COMMAND_METHODS = ['get', 'set', 'merge', 'delete'] as const const CONDITION_SOURCES = ['input', 'context'] as const const LOGICAL_OPERATORS = ['or', 'and'] as const const TYPE_OF_STATE_ID = ['state', 'variable'] as const const createVariableNameSchema = (description: string) => z.string().describe(description) const plainTextSchema = z.string() const mediaLinkSchema = z.object({ title: z.string().optional(), text: z.string().optional(), // Mime type of the linked content. type: z.string().describe('Mime type of the content'), uri: z.string(), }) const selectSchema = z.object({ text: z.string(), scope: z.enum(SELECT_SCOPES).nullish(), // Builder accepts both simple string options and object options. options: z.array(z.string()).or( z.array( z.object({ text: z.string(), }), ), ), }) const chatstateSchema = z.object({ state: z.string(), interval: z.union([z.number(), z.string()]).optional(), }) const webLinkSchema = z.object({ title: z.string().nullish(), text: z.string(), target: z.enum(LINK_TARGETS).nullish(), uri: z.string(), }) const flowMessageContentSchema = plainTextSchema .or(mediaLinkSchema) .or(selectSchema) .or(webLinkSchema) .or(chatstateSchema) const processHttpActionSettingsSchema = z.object({ $invalid: z.boolean().optional(), method: z.enum(HTTP_METHODS), // The URL to be called. Variables can be interpolated, for example: // https://api.example.com/{{contact.email}} uri: z.string(), // The headers to be sent in the request. Variables can be interpolated too. headers: z.record(z.string(), z.string()).nullish(), // The body of the request. Variables can be interpolated in the body too. body: z.string(), // Context variable used to store the response body. responseBodyVariable: createVariableNameSchema('Response body variable name'), // Context variable used to store the response status. responseStatusVariable: createVariableNameSchema('Response status variable name'), }) const trackEventActionSettingsSchema = z.object({ $invalid: z.boolean().optional(), category: z.string(), action: z.string(), extras: z.record(z.string(), z.string()).nullish(), }) const mergeContactActionSettingsSchema = z.object({ $invalid: z.boolean().optional(), name: z.string().nullish(), email: z.string().nullish(), city: z.string().nullish(), extras: z.record(z.string(), z.string()).nullish(), }) const executeScriptActionSettingsSchema = z.object({ $invalid: z.boolean().optional(), // Builder executes a function called "run". function: z.literal('run').optional(), // Must be valid ES5 source code. source: z.string(), // Variables are passed to the function in this declared order. Do not use // the {{ variableName }} syntax here, only the plain variable name. inputVariables: z.array(createVariableNameSchema('Variable name')), // Context variable used to store the returned result. outputVariable: createVariableNameSchema('Output variable name'), }) const sendMessageActionSettingsSchema = z.object({ $invalid: z.boolean().optional(), type: z.enum(MIME_TYPES).optional(), content: flowMessageContentSchema, // Message id generated by the builder. id: z.string().optional(), metadata: z.record(z.string(), z.string()).optional(), }) const sendRawMessageActionSettingsSchema = z.object({ $invalid: z.boolean().optional(), type: z.literal('application/json').optional(), // Useful to send raw channel payloads, such as WhatsApp interactive content. rawContent: z.string(), }) const setVariableActionSettingsSchema = z.object({ $invalid: z.boolean().optional(), variable: createVariableNameSchema('Name of the variable to be set in the context'), value: z.union([z.string(), z.number()]).optional(), // Expiration time in seconds. expiration: z.number().nullish(), }) const processCommandActionSettingsSchema = z.object({ $invalid: z.boolean().optional(), to: z.string(), method: z.enum(COMMAND_METHODS), uri: z.string(), variable: createVariableNameSchema('Variable name'), from: z.string().optional(), }) const forwardToDeskActionSettingsSchema = z.object({ $invalid: z.boolean().optional(), }) const leavingFromDeskActionSettingsSchema = z.object({ $invalid: z.boolean().optional(), }) const redirectActionSettingsSchema = z.object({ $invalid: z.boolean().optional(), address: z.string(), context: z.object({ type: z.literal('text/plain'), value: z.string(), }), }) export const conditionSchema = z.object({ source: z.enum(CONDITION_SOURCES), // Used only when source is "context". variable: createVariableNameSchema('Context variable name').nullish(), comparison: z.enum(COMPARISON_OPERATORS), operator: z.enum(LOGICAL_OPERATORS).optional(), values: z.array(z.string()), }) const baseActionSchema = z.object({ $id: z.string(), // Should only exist for SendMessage and SendRawMessage actions. When it is // omitted, GET responses usually return an empty string. $typeOfContent: z.enum(CONTENT_TYPES).optional(), $title: z.string().optional(), $invalid: z.boolean().optional(), // The action runs only when its conditions are met. conditions: z.array(conditionSchema).optional(), $cardContent: z .object({ editable: z.boolean(), deletable: z.boolean(), position: z.literal('left'), document: z.object({ id: z.string(), // Must match the type defined in the action settings. type: z.enum(MIME_TYPES).or(z.literal('application/json')), // Must match the content defined in the action settings. content: flowMessageContentSchema.or(z.string()), textContent: z.string().optional(), }), }) .nullish(), // When true, execution continues even if the action fails. continueOnError: z.boolean().optional(), }) const actionTypeSchema = z.discriminatedUnion('type', [ z.object({ type: z.literal('SendMessage'), settings: sendMessageActionSettingsSchema, }), z.object({ type: z.literal('SendRawMessage'), settings: sendRawMessageActionSettingsSchema, }), z.object({ type: z.literal('TrackEvent'), settings: trackEventActionSettingsSchema, }), z.object({ type: z.literal('ProcessHttp'), settings: processHttpActionSettingsSchema, }), z.object({ type: z.literal('MergeContact'), settings: mergeContactActionSettingsSchema, }), z.object({ type: z.literal('ExecuteScript'), settings: executeScriptActionSettingsSchema, }), z.object({ type: z.literal('ExecuteScriptV2'), settings: executeScriptActionSettingsSchema, }), z.object({ type: z.literal('SetVariable'), settings: setVariableActionSettingsSchema, }), z.object({ type: z.literal('ProcessCommand'), settings: processCommandActionSettingsSchema, }), z.object({ type: z.literal('ForwardToDesk'), settings: forwardToDeskActionSettingsSchema, }), z.object({ type: z.literal('LeavingFromDesk'), settings: leavingFromDeskActionSettingsSchema, }), z.object({ type: z.literal('Redirect'), settings: redirectActionSettingsSchema, }), ]) export const actionSchema = z.intersection(baseActionSchema, actionTypeSchema) const inputValidationSchema = z.object({ // Validation rule applied to the captured input. rule: z.enum(VALIDATION_RULES), regex: z.string().nullish(), type: z.string().nullish(), // Message shown when validation fails. error: z.string(), }) export const inputStateSchema = z.object({ // Builder card metadata shown in the editor. The values are usually fixed // except for the nested document id. $cardContent: z.object({ document: z.object({ id: z.string(), type: z.literal('text/plain'), // Builder commonly renders "Entrada do usuario" here. content: z.string().optional(), // Should usually match the variable name where the input is stored. textContent: z.string().optional(), }), editable: z.boolean(), editing: z.boolean(), deletable: z.boolean(), position: z.literal('right'), }), $invalid: z.boolean(), // When true, the input step is skipped. bypass: z.boolean(), validation: inputValidationSchema.nullish(), // Input expiration time in minutes. expiration: z.string().nullish(), variable: createVariableNameSchema('Input variable name').nullish(), conditions: z.array(conditionSchema).optional(), }) const baseOutputSchema = z.object({ // Id of the target state, or a variable id when typeOfStateId is "variable". stateId: z.string(), typeOfStateId: z.enum(TYPE_OF_STATE_ID).optional(), }) export const conditionOutputSchema = baseOutputSchema.extend({ $id: z.string().optional(), $invalid: z.boolean().optional(), $connId: z.string().optional(), $contentId: z.string().optional(), $isDeskOutput: z.boolean().optional(), $isDeskCustomOutput: z.boolean().optional(), $isDeskDefaultOutput: z.boolean().optional(), conditions: z.array(conditionSchema), }) export const defaultOutputSchema = baseOutputSchema.extend({ $invalid: z.boolean().optional(), }) export const contentActionItemSchema = z.object({ // Actions executed after entering actions. In practice, this is where // builder stores SendMessage cards. action: actionSchema.optional(), // If the state expects user input, it should be defined here. input: inputStateSchema.optional(), $invalid: z.boolean().optional(), }) export const stateSchema = z.object({ $contentActions: z.array(contentActionItemSchema), $conditionOutputs: z.array(conditionOutputSchema), // Actions executed when entering the state. $enteringCustomActions: z.array(actionSchema), // Actions executed when leaving the state, before output evaluation. $leavingCustomActions: z.array(actionSchema), $localCustomActions: z.array(actionSchema).optional(), $inputSuggestions: z.array(z.string()), // Output used when none of the conditional outputs are matched. $defaultOutput: defaultOutputSchema, isAiGenerated: z.boolean().optional(), deskStateVersion: z.string().optional(), $afterStateChangedActions: z.array(actionSchema).optional(), $tags: z.array( z.object({ id: z.string(), label: z.string(), // Hexadecimal color code including the "#" prefix. background: z.string(), canChangeBackground: z.boolean(), }), ), id: z.string().optional(), // Only the special state with id "onboarding" is usually marked as root. root: z.boolean().optional(), $title: z.string(), $position: z .object({ top: z.string(), left: z.string(), }) .optional(), $invalidContentActions: z.boolean(), $invalidOutputs: z.boolean(), $invalidCustomActions: z.boolean(), $invalid: z.boolean().optional(), $whiteWallIntegration: z.unknown().optional(), }) export const flowSchema = z.record(z.string(), stateSchema) export type Condition = z.infer export type Action = z.infer export type InputState = z.infer export type ConditionOutput = z.infer export type DefaultOutput = z.infer export type ContentActionItem = z.infer export type State = z.infer export type Flow = z.infer export type BuilderFlow = Flow export type BuilderConfiguration = { 'builder:minimumIntentScore'?: string 'builder:stateTrack'?: string 'builder:#localTimeZone'?: string } & Record // The history index is a number from 1 to 10, representing the last 10 publications. export type HistoryIndex = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 export type Publication = { authorIdentity: string author: string } export type BuilderLatestPublications = { // The publication index rotates from 1 to 10. lastInsertedIndex: HistoryIndex isMoreOptionsActive: string publications: Array } export type BuilderLatestPublication = { flow: BuilderFlow globalActions?: State configuration?: BuilderConfiguration } type RecursivelyRemoveDollar = T extends object ? T extends Array ? Array> : { [Key in keyof T as Key extends `$${string}` ? never : Key]: RecursivelyRemoveDollar } : T type TypeOfStateId = (typeof TYPE_OF_STATE_ID)[number] export type StateApplication = { id: string root?: boolean name: string deskStateVersion?: string inputActions: RecursivelyRemoveDollar> input: RecursivelyRemoveDollar outputActions: RecursivelyRemoveDollar> afterStateChangedActions: RecursivelyRemoveDollar> outputs: Array<{ stateId: string typeOfStateId?: TypeOfStateId conditions?: RecursivelyRemoveDollar> }> } export type ApplicationFlow = { identifier: string messageReceivers: Array<{ state: string type: string }> notificationReceivers: Array<{ type: string }> serviceProviderType: string settings: { flow: { id: string version: number input?: RecursivelyRemoveDollar states: Array configuration: BuilderConfiguration inputActions: Array> outputActions: Array> type: 'flow' } } settingsType: 'Settings' }