@scout9/app 1.0.0-alpha.0.1.91 → 1.0.0-alpha.0.1.93

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,15 +6,17 @@ import { agentConfigurationSchema, customerSchema } from './users.js';
6
6
  import { MessageSchema } from './message.js';
7
7
 
8
8
 
9
-
10
9
  /**
11
10
  * @typedef {import('zod').infer<typeof WorkflowConfigurationSchema>} IWorkflowConfiguration
12
11
  */
13
12
  export const WorkflowConfigurationSchema = z.object({
14
- entities: z.array(zId('Workflow Folder', z.string()), {description: 'Workflow id association, used to handle route params'})
13
+ entities: z.array(
14
+ zId('Workflow Folder', z.string()),
15
+ {description: 'Workflow id association, used to handle route params'}
16
+ )
15
17
  .min(1, 'Must have at least 1 entity')
16
18
  .max(15, 'Cannot have more than 15 entity paths'),
17
- entity: zId('Workflow Folder', z.string()),
19
+ entity: zId('Workflow Folder', z.string())
18
20
  });
19
21
 
20
22
  /**
@@ -29,11 +31,12 @@ export const WorkflowsConfigurationSchema = z.array(WorkflowConfigurationSchema)
29
31
  export const ConversationSchema = z.object({
30
32
  $agent: zId('Conversation Agent ID', z.string({description: 'Default agent assigned to the conversation(s)'})),
31
33
  $customer: zId('Conversation Customer ID', z.string({description: 'Customer this conversation is with'})),
32
- initialContexts: z.array(z.string(), {description: 'Initial contexts to load when starting the conversation'}).optional(),
34
+ initialContexts: z.array(z.string(), {description: 'Initial contexts to load when starting the conversation'})
35
+ .optional(),
33
36
  environment: z.enum(['phone', 'email', 'web']),
34
37
  environmentProps: z.object({
35
38
  subject: z.string({description: 'HTML Subject of the conversation'}).optional(),
36
- platformEmailThreadId: z.string({description: 'Used to sync email messages with the conversation'}).optional(),
39
+ platformEmailThreadId: z.string({description: 'Used to sync email messages with the conversation'}).optional()
37
40
  }).optional(),
38
41
  locked: z.boolean({description: 'Whether the conversation is locked or not'}).optional().nullable(),
39
42
  lockedReason: z.string({description: 'Why this conversation was locked'}).optional().nullable(),
@@ -42,16 +45,16 @@ export const ConversationSchema = z.object({
42
45
  forwarded: z.string({description: 'Datetime ISO 8601 timestamp when persona was forwarded'}).optional().nullable(),
43
46
  forwardNote: z.string().optional().nullable(),
44
47
  intent: z.string({description: 'Detected intent of conversation'}).optional().nullable(),
45
- intentScore: z.number({description: 'Confidence score of the assigned intent'}).optional().nullable(),
48
+ intentScore: z.number({description: 'Confidence score of the assigned intent'}).optional().nullable()
46
49
  });
47
50
 
48
51
  /**
49
52
  * @typedef {import('zod').infer<typeof IntentWorkflowEventSchema>} IIntentWorkflowEvent
50
53
  */
51
54
  export const IntentWorkflowEventSchema = z.object({
52
- current: z.string().nullable(),
53
- flow: z.array(z.string()),
54
- initial: z.string().nullable()
55
+ current: z.string().nullable(),
56
+ flow: z.array(z.string()),
57
+ initial: z.string().nullable()
55
58
  });
56
59
 
57
60
  /**
@@ -74,7 +77,7 @@ export const WorkflowEventSchema = z.object({
74
77
  intent: IntentWorkflowEventSchema,
75
78
  stagnationCount: z.number(),
76
79
  note: z.string({description: 'Any developer notes to provide'}).optional()
77
- })
80
+ });
78
81
 
79
82
  const Primitive = z.union([z.string(), z.number(), z.boolean()]);
80
83
  // Assuming ConversationContext is already defined as a Zod schema
@@ -102,29 +105,36 @@ export const ForwardSchema = z.union([
102
105
  to: z.string().optional(),
103
106
  mode: z.enum(['after-reply', 'immediately']).optional(),
104
107
  note: z.string({description: 'Note to provide to the agent'}).optional()
105
- }),
108
+ })
106
109
  ], {description: 'Forward input information of a conversation'});
107
110
 
108
111
 
109
112
  /**
110
113
  * Instruction object schema used to send context to guide conversations
111
- * @typedef {import('zod').infer<typeof InstructionSchema>} IInstruction
114
+ * @typedef {import('zod').infer<typeof InstructionObjectSchema>} IInstruction
112
115
  */
113
- export const InstructionSchema = z.object({
114
- id: zId('Instruction ID').describe('Unique ID for the instruction, this is used to remove the instruction later'),
115
- content: z.string(),
116
+ export const InstructionObjectSchema = z.object({
117
+ id: zId('Instruction ID')
118
+ .describe('Unique ID for the instruction, this is used to remove the instruction later')
119
+ .optional(),
120
+ persist: z.boolean()
121
+ .describe(
122
+ 'if true, the instruction persists the conversation, if false the instruction will only last for 1 auto reply')
123
+ .default(true)
124
+ .optional(),
125
+ content: z.string()
116
126
  });
117
127
 
118
128
  /**
119
129
  * @typedef {import('zod').infer<typeof WorkflowResponseMessageApiRequest>} IWorkflowResponseMessageApiRequest
120
130
  */
121
- export const WorkflowResponseMessageApiRequest = z.object({
131
+ export const WorkflowResponseMessageApiRequest = z.object({
122
132
  uri: z.string(),
123
133
  data: z.any().optional(),
124
134
  headers: z.object({
125
- [z.string()]: z.string(),
135
+ [z.string()]: z.string()
126
136
  }).optional(),
127
- method: z.enum(["GET", "POST", "PUT"]).optional()
137
+ method: z.enum(['GET', 'POST', 'PUT']).optional()
128
138
  });
129
139
 
130
140
  /**
@@ -168,19 +178,67 @@ export const WorkflowResponseMessageApiResponse = z.union([
168
178
 
169
179
  /**
170
180
  * The workflow response object slot
171
- * @typedef {import('zod').infer<typeof WorkflowResponseSlotSchema>} IWorkflowResponseSlot
181
+ * @typedef {import('zod').infer<typeof InstructionSchema>} IInstruction
182
+ */
183
+ export const InstructionSchema = z.union([z.string(), InstructionObjectSchema, z.array(z.string()), z.array(
184
+ InstructionObjectSchema)]);
185
+
186
+ /**
187
+ * Base follow up schema to follow up with the client
188
+ * @typedef {import('zod').infer<typeof FollowupBaseSchema>} IFollowupBase
189
+ */
190
+ export const FollowupBaseSchema = z.object({
191
+ scheduled: z.number(),
192
+ cancelIf: ConversationContext.optional(),
193
+ overrideLock: z.boolean({description: 'This will still run even if the conversation is locked, defaults to false'}).optional()
194
+ });
195
+
196
+ /**
197
+ * Data used to automatically follow up with the client in the future
198
+ * @typedef {import('zod').infer<typeof FollowupSchema>} IFollowup
199
+ */
200
+ export const FollowupSchema = z.union([
201
+ FollowupBaseSchema.extend({
202
+ message: z.string({description: 'Manual message sent to client'}),
203
+ }),
204
+ FollowupBaseSchema.extend({
205
+ instructions: InstructionSchema
206
+ })
207
+ ]);
208
+
209
+ /**
210
+ * The workflow response object slot
211
+ * @typedef {import('zod').infer<typeof WorkflowResponseSlotBaseSchema>} IWorkflowResponseSlotBase
172
212
  */
173
- export const WorkflowResponseSlotSchema = z.object({
213
+ export const WorkflowResponseSlotBaseSchema = z.object({
174
214
  forward: ForwardSchema.optional(),
175
- forwardNote: z.string({description: 'Note to provide to the agent, recommend using forward object api instead'}).optional(),
176
- instructions: z.union([z.string(), InstructionSchema, z.array(z.string()), z.array(InstructionSchema)]).optional(),
215
+ forwardNote: z.string({description: 'Note to provide to the agent, recommend using forward object api instead'})
216
+ .optional(),
217
+ instructions: InstructionSchema.optional(),
177
218
  removeInstructions: z.array(z.string()).optional(),
178
219
  message: z.string().optional(),
179
- // message: WorkflowResponseMessage.optional(),
180
220
  secondsDelay: z.number().optional(),
181
221
  scheduled: z.number().optional(),
182
222
  contextUpsert: ConversationContext.optional(),
183
223
  resetIntent: z.boolean().optional(),
224
+ followup: FollowupSchema.optional()
225
+ });
226
+
227
+ /**
228
+ * The workflow response object slot
229
+ * @typedef {import('zod').infer<typeof WorkflowResponseSlotSchema>} IWorkflowResponseSlot
230
+ */
231
+ export const WorkflowResponseSlotSchema = WorkflowResponseSlotBaseSchema.extend({
232
+ anticipate: z.union([
233
+ z.object({
234
+ did: z.string({definition: 'The prompt to check if true or false'}),
235
+ yes: WorkflowResponseSlotBaseSchema,
236
+ no: WorkflowResponseSlotBaseSchema
237
+ }),
238
+ z.array(WorkflowResponseSlotBaseSchema.extend({
239
+ keywords: z.array(z.string()).min(1).max(20)
240
+ }))
241
+ ]).optional()
184
242
  });
185
243
 
186
244
  /**
@@ -199,5 +257,5 @@ export const WorkflowFunctionSchema = z.function()
199
257
  .args(WorkflowEventSchema)
200
258
  .returns(z.union([
201
259
  z.promise(WorkflowResponseSchema),
202
- WorkflowResponseSchema,
260
+ WorkflowResponseSchema
203
261
  ]));
@@ -1,2 +1,3 @@
1
1
  export * from './entry.js';
2
2
  export * from './client/index.js';
3
+ export * from './macros/index.js';
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Builder Macros
3
+ * used to build and guide a application logic in relation to the Scout9 conversation state.
4
+ */
5
+ import { Configuration, Scout9Api } from '@scout9/admin';
6
+ import MacroGlobals from './globals.js';
7
+
8
+ function handleAxiosResponse(res) {
9
+ if (res.status === 200) {
10
+ return res.data;
11
+ } else {
12
+ throw new Error(`${res.status} ${res.statusText}`)
13
+ }
14
+ }
15
+
16
+ /**
17
+ * The `did` macro takes a given prompt and infers a binary `true` or `false` result in relation to the prompt's subject actor and the prompt's inquiry.
18
+ * @param {string} prompt
19
+ * @return {Promise<boolean>}
20
+ */
21
+ export async function did(prompt) {
22
+ const convoId = MacroGlobals.$convo();
23
+ const {value} = (await (new Scout9Api(new Configuration({apiKey: process.env.SCOUT9_API_KEY}))).did({prompt, convoId})
24
+ .then(handleAxiosResponse));
25
+ return value;
26
+ }
27
+
28
+
29
+ /**
30
+ * The `context` macro, similar to the `did` macro, takes a natural statement and checks the entire conversation state and extracts or infers a metadata composition result.
31
+ * @param {string} prompt
32
+ * @param {import('@scout9/admin').CaptureContextRequestExamples} [examples]
33
+ * @return {Promise<import('@scout9/admin').CaptureContext200Response>}
34
+ */
35
+ export async function context(prompt, examples) {
36
+ const convoId = MacroGlobals.$convo();
37
+ return (await (new Scout9Api(new Configuration({apiKey: process.env.SCOUT9_API_KEY}))).captureContext({
38
+ prompt,
39
+ examples,
40
+ convoId
41
+ })
42
+ .then(handleAxiosResponse));
43
+ }
@@ -0,0 +1,234 @@
1
+ import { WorkflowResponseSlotBaseSchema, WorkflowResponseSlotSchema } from '../client/workflow.js';
2
+ import { MacroUtils } from './utils.js';
3
+
4
+
5
+
6
+ function EventMacros() {
7
+
8
+ let slots = [];
9
+
10
+ return {
11
+
12
+ /**
13
+ * Sets context into the conversation context for later use
14
+ * @param {Record<string, any>} updates
15
+ * @return {this}
16
+ */
17
+ upsert(updates) {
18
+ const slot = {contextUpsert: updates}
19
+ slots.push(WorkflowResponseSlotSchema.parse(slot));
20
+ return this;
21
+ },
22
+
23
+ /**
24
+ * Similar to `instruction` except that it requires a schedule time parameter that determines when to follow up (and is not an event output macro). This will fire another run job with a new insert system context message, if `options.literal` is set to true, it will be an appended agent message prior to running the workflow app.
25
+ *
26
+ * @param {string} instruction
27
+ *
28
+ * @overload
29
+ * @param {Date | string} options
30
+ *
31
+ * @overload
32
+ * @param {Object} options
33
+ * @param {Date | string} options.scheduled
34
+ * @param {Record<string, any>} [options.cancelIf]
35
+ * @param {boolean} [options.literal]
36
+ * @param {boolean} [options.overrideLock]
37
+ *
38
+ * @return {this}
39
+ */
40
+ followup(instruction, options) {
41
+ let slot;
42
+ if (!options) {
43
+ throw new Error('Missing second argument in followup(instruction, options) \'options\' argument needs to be included')
44
+ }
45
+ // Check if it's date value
46
+ const {success, ...rest} = MacroUtils.scheduledToUnixSafe(options);
47
+ if (!success) {
48
+ if (!('scheduled' in options)) {
49
+ throw new Error('.scheduled was not included in options and needs to be required');
50
+ }
51
+ if (typeof options !== 'object') {
52
+ throw new Error('second argument options in followup is not an object type');
53
+ }
54
+
55
+ // Advance object
56
+ slot = {
57
+ followup: {
58
+ scheduled: MacroUtils.scheduledToUnix(options.scheduled)
59
+ }
60
+ }
61
+ if (options.literal) {
62
+ slot.followup.message = instruction;
63
+ } else {
64
+ slot.followup.instructions = instruction;
65
+ }
66
+ if (typeof options.overrideLock === 'boolean') {
67
+ slot.followup.overrideLock = options.overrideLock;
68
+ }
69
+ if (options.cancelIf) {
70
+ slot.followup.cancelIf = options.cancelIf;
71
+ }
72
+
73
+ } else {
74
+ // Simple follow up
75
+ slot = {
76
+ followup: {
77
+ instructions: instruction,
78
+ scheduled: rest.data
79
+ }
80
+ }
81
+
82
+ }
83
+ slots.push(WorkflowResponseSlotSchema.parse(slot));
84
+ return this;
85
+ },
86
+
87
+ /**
88
+ * Similar to `instruct` except that it requires a schedule time parameter that determines when to follow up (and is not an event output macro). This will fire another run job with a new insert system context message, if `options.literal` is set to true, it will be an appended agent message prior to running the workflow app.
89
+ * @overload
90
+ * @param {string} instruction - The instruction to be anticipated as a string. When this is a string, `yes` and `no` must be provided as objects.
91
+ * @param {object} yes - The object to process if the instruction is a string and the condition is met.
92
+ * @param {object} no - The object to process if the instruction is a string and the condition is not met.
93
+ *
94
+ * @overload
95
+ * @param {(Array<import('zod').infer<typeof import('../runtime/client/workflow.js').WorkflowResponseSlotBaseSchema> & {keywords: string[]}>)} instruction
96
+ *
97
+ * @return {EventMacros}
98
+ */
99
+ anticipate(instruction, yes, no) {
100
+ const slot = {
101
+ anticipate: []
102
+ }
103
+ if (Array.isArray(instruction)) {
104
+ for (const _slot of instruction) {
105
+ if (!Array.isArray(_slot.keywords) || _slot.keywords.length < 1) {
106
+ throw new Error(`Anticipate field .keywords should be an array of string, with more than one value`);
107
+ }
108
+ slot.anticipate.push(_slot);
109
+ }
110
+ } else if (typeof instruction === 'string') {
111
+ if (!yes) {
112
+ throw new Error('Missing "yes" option for anticipate');
113
+ }
114
+ if (!no) {
115
+ throw new Error('Missing "no" option for anticipate');
116
+ }
117
+ slot.anticipate = {
118
+ did: instruction,
119
+ yes: WorkflowResponseSlotBaseSchema.parse(yes),
120
+ no: WorkflowResponseSlotBaseSchema.parse(no)
121
+ }
122
+ } else {
123
+ throw new Error(`Instruction is not of type "string" or "array"`);
124
+ }
125
+ slots.push(WorkflowResponseSlotSchema.parse(slot));
126
+ return this;
127
+ },
128
+
129
+ /**
130
+ *
131
+ * @param {string} instruction
132
+ * @param {Object} [options]
133
+ * @param {string} [options.id] - Unique ID for the instruction, this is used to remove the instruction later
134
+ * @param {string} [options.persist] - if true, the instruction persists the conversation, if false the instruction will only last for 1 auto reply
135
+ * @return {EventMacros}
136
+ */
137
+ instruct(instruction, options = {}) {
138
+ const slot = {};
139
+ if (Object.keys(options).length > 0) {
140
+ const instructionObj = {
141
+ content: instruction,
142
+ }
143
+ if (options.id) {
144
+ instructionObj.id = options.id
145
+ }
146
+ if (typeof options.persist === 'boolean') {
147
+ instructionObj.persist = options.persist
148
+ }
149
+ slot.instructions = instructionObj;
150
+ } else {
151
+ slot.instructions = instruction;
152
+ }
153
+ slots.push(WorkflowResponseSlotSchema.parse(slot));
154
+ return this;
155
+ },
156
+ /**
157
+ * If a manual message must be sent, you can use the `reply` macro
158
+ * @param {string} message - the message to manually send to the user
159
+ * @param {Object} [options]
160
+ * @param {Date | string} [options.scheduled] - this will schedule the date to a specific provided date
161
+ * @param {string} [options.delay] - delays the message return in seconds
162
+ * @return {this}
163
+ */
164
+ reply(message, options = {}) {
165
+ const slot = {
166
+ message
167
+ };
168
+ if (Object.keys(options).length) {
169
+ if ('scheduled' in options) {
170
+ slot.scheduled = MacroUtils.scheduledToUnix(options.scheduled);
171
+ }
172
+ if ('delay' in options) {
173
+ const delay = options.delay;
174
+ if (typeof delay !== 'number') {
175
+ throw new Error('.delay is not of type "number"');
176
+ }
177
+ slot.secondsDelay = delay;
178
+ }
179
+ }
180
+ slots.push(WorkflowResponseSlotSchema.parse(slot));
181
+ return this;
182
+ },
183
+ /**
184
+ * This macro ends the conversation and forwards it the owner of the persona to manually handle the flow. If your app returns undefined or no event, then a default forward is generated.
185
+ * @param {string} message - the message to forward to owner of persona
186
+ * @param {Object} [options]
187
+ * @param {'after-reply' | 'immediately'} [options.mode] - sets forward mode, defaults to "immediately"
188
+ * @param {string} [options.to] - another phone or email to forward to instead of owner
189
+ * @return {this}
190
+ */
191
+ forward(message, options = {}) {
192
+ let slot;
193
+ if (options && Object.keys(options).length) {
194
+ slot = {
195
+ forward: {
196
+ note: message
197
+ },
198
+ forwardNote: message
199
+ }
200
+ if (options.to) {
201
+ slot.forward.to = options.to;
202
+ }
203
+ if (options.mode) {
204
+ slot.forward.mode = options.mode;
205
+ }
206
+ } else {
207
+ slot = {
208
+ forward: true,
209
+ forwardNote: message
210
+ }
211
+ }
212
+ slots.push(WorkflowResponseSlotSchema.parse(slot));
213
+ return this;
214
+ },
215
+ /**
216
+ *
217
+ * @return {Array<(import('zod').infer<typeof import('../runtime/client/workflow.js').WorkflowResponseSlotSchema>)>}
218
+ */
219
+ toJSON(flush = true) {
220
+ if (flush) {
221
+ const copy = [...slots];
222
+ slots =[];
223
+ return copy;
224
+ } else {
225
+ return slots;
226
+ }
227
+ },
228
+ };
229
+ }
230
+
231
+ const eventMacros = EventMacros();
232
+ export const instruct = eventMacros.instruct.bind(eventMacros);
233
+ export const forward = eventMacros.forward.bind(eventMacros);
234
+ export const reply = eventMacros.reply.bind(eventMacros);
@@ -0,0 +1,14 @@
1
+
2
+
3
+ export default class MacroGlobals {
4
+
5
+ static $convo() {
6
+ const $convo = globalThis?.SCOUT9?.$convo;
7
+ if (!$convo) {
8
+ throw new Error(`$convo not found in runtime context, ${MacroGlobals.#hint}`);
9
+ }
10
+ return $convo;
11
+ }
12
+
13
+ static #hint = `make sure the context is properly instantiated before running workflow.`;
14
+ }
@@ -0,0 +1,3 @@
1
+ export * from './event.js';
2
+ export * from './builder.js';
3
+ export * from './schemas.js';
@@ -0,0 +1,12 @@
1
+ import { z } from "zod";
2
+
3
+ export const ContextExampleWithTrainingDataSchema = z.object({
4
+ input: z.string(),
5
+ output: z.array(z.record(z.any())),
6
+ });
7
+
8
+ export const ContextExampleSchema = z.union([
9
+ z.array(ContextExampleWithTrainingDataSchema),
10
+ z.array(z.record(z.any())),
11
+ ]);
12
+
@@ -0,0 +1,31 @@
1
+ function MacroUtilsFactory() {
2
+
3
+ return {
4
+ dateToUnix(date) { return parseInt((date.getTime() / 1000).toFixed(0))},
5
+ scheduledToUnixSafe(scheduled) {
6
+ if (scheduled instanceof Date) {
7
+ return {data: this.dateToUnix(scheduled), success: true}
8
+ } else if (typeof scheduled === 'string') {
9
+ const timestamp = Date.parse(scheduled);
10
+ if (isNaN(timestamp) === false) {
11
+ return {data: this.dateToUnix(new Date(timestamp)), success: true };
12
+ } else {
13
+ return {error: '.scheduled is an invalid date string', success: false};
14
+ }
15
+ } else {
16
+ return {error: `.scheduled was neither a Date or ISO string`, success: false};
17
+ }
18
+ },
19
+ scheduledToUnix(scheduled) {
20
+ const {success, ...rest} = this.scheduledToUnixSafe(scheduled);
21
+ if (success) {
22
+ return rest.data;
23
+ } else {
24
+ throw new Error(rest.error);
25
+ }
26
+ }
27
+ }
28
+
29
+ }
30
+
31
+ export const MacroUtils = MacroUtilsFactory();