@scout9/app 1.0.0-alpha.0.1.9 → 1.0.0-alpha.0.1.90

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.
Files changed (70) hide show
  1. package/README.md +32 -0
  2. package/dist/{index-92deaa5f.cjs → exports-e7d51b70.cjs} +46618 -4591
  3. package/dist/index.cjs +58 -15
  4. package/dist/{multipart-parser-090f08a9.cjs → multipart-parser-e09a67c9.cjs} +13 -7
  5. package/dist/spirits-3b603262.cjs +1218 -0
  6. package/dist/spirits.cjs +9 -0
  7. package/dist/testing-tools.cjs +48 -0
  8. package/package.json +30 -8
  9. package/src/cli.js +162 -69
  10. package/src/core/config/agents.js +300 -7
  11. package/src/core/config/entities.js +58 -28
  12. package/src/core/config/index.js +37 -15
  13. package/src/core/config/project.js +160 -6
  14. package/src/core/config/workflow.js +13 -12
  15. package/src/core/data.js +27 -0
  16. package/src/core/index.js +386 -137
  17. package/src/core/sync.js +71 -0
  18. package/src/core/templates/Dockerfile +22 -0
  19. package/src/core/templates/app.js +453 -0
  20. package/src/core/templates/project-files.js +36 -0
  21. package/src/core/templates/template-package.json +13 -0
  22. package/src/exports.js +21 -17
  23. package/src/platform.js +189 -33
  24. package/src/public.d.ts.text +330 -0
  25. package/src/report.js +117 -0
  26. package/src/runtime/client/api.js +56 -159
  27. package/src/runtime/client/config.js +60 -11
  28. package/src/runtime/client/entity.js +19 -6
  29. package/src/runtime/client/index.js +5 -3
  30. package/src/runtime/client/message.js +13 -3
  31. package/src/runtime/client/platform.js +86 -0
  32. package/src/runtime/client/{agent.js → users.js} +35 -3
  33. package/src/runtime/client/utils.js +10 -9
  34. package/src/runtime/client/workflow.js +131 -9
  35. package/src/runtime/entry.js +2 -2
  36. package/src/testing-tools/dev.js +373 -0
  37. package/src/testing-tools/index.js +1 -0
  38. package/src/testing-tools/mocks.js +37 -5
  39. package/src/testing-tools/spirits.js +530 -0
  40. package/src/utils/audio-buffer.js +16 -0
  41. package/src/utils/audio-type.js +27 -0
  42. package/src/utils/configs/agents.js +68 -0
  43. package/src/utils/configs/entities.js +145 -0
  44. package/src/utils/configs/project.js +23 -0
  45. package/src/utils/configs/workflow.js +47 -0
  46. package/src/utils/file-type.js +569 -0
  47. package/src/utils/file.js +158 -0
  48. package/src/utils/glob.js +30 -0
  49. package/src/utils/image-buffer.js +23 -0
  50. package/src/utils/image-type.js +39 -0
  51. package/src/utils/index.js +1 -0
  52. package/src/utils/is-svg.js +37 -0
  53. package/src/utils/logger.js +111 -0
  54. package/src/utils/module.js +14 -25
  55. package/src/utils/project-templates.js +191 -0
  56. package/src/utils/project.js +387 -0
  57. package/src/utils/video-type.js +29 -0
  58. package/types/index.d.ts +7588 -206
  59. package/types/index.d.ts.map +97 -22
  60. package/dist/index-1b8d7dd2.cjs +0 -49555
  61. package/dist/index-2ccb115e.cjs +0 -49514
  62. package/dist/index-66b06a30.cjs +0 -49549
  63. package/dist/index-bc029a1d.cjs +0 -49528
  64. package/dist/index-d9a93523.cjs +0 -49527
  65. package/dist/multipart-parser-1508046a.cjs +0 -413
  66. package/dist/multipart-parser-7007403a.cjs +0 -413
  67. package/dist/multipart-parser-70c32c1d.cjs +0 -413
  68. package/dist/multipart-parser-71dec101.cjs +0 -413
  69. package/dist/multipart-parser-f15bf2e0.cjs +0 -414
  70. package/src/public.d.ts +0 -209
@@ -2,8 +2,14 @@ import { z } from 'zod';
2
2
  import { zId } from './utils.js';
3
3
  import { MessageSchema } from './message.js';
4
4
 
5
+ /**
6
+ * @typedef {import('zod').infer<typeof customerValueSchema>} ICustomerValue
7
+ */
5
8
  export const customerValueSchema = z.union([z.boolean(), z.number(), z.string()]);
6
9
 
10
+ /**
11
+ * @typedef {import('zod').infer<typeof customerSchema>} ICustomer
12
+ */
7
13
  export const customerSchema = z.object({
8
14
  firstName: z.string().optional(),
9
15
  lastName: z.string().optional(),
@@ -24,8 +30,16 @@ export const customerSchema = z.object({
24
30
  stripeDev: z.string().nullable().optional()
25
31
  }).catchall(customerValueSchema);
26
32
 
27
- export const agentConfigurationSchema = z.object({
28
- id: zId('Agent ID', z.string({description: 'Unique ID for agent'})),
33
+ /**
34
+ * @typedef {import('zod').infer<typeof agentBaseConfigurationSchema>} IAgentBase
35
+ */
36
+ export const agentBaseConfigurationSchema = z.object({
37
+ deployed: z.object({
38
+ web: z.string({description: 'Web URL for agent'}).optional(),
39
+ phone: z.string({description: 'Phone number for agent'}).optional(),
40
+ email: z.string({description: 'Email address for agent'}).optional()
41
+ }).optional(),
42
+ img: z.string().nullable().optional(),
29
43
  firstName: z.string({description: 'Agent first name'}).optional(),
30
44
  lastName: z.string({description: 'Agent last name'}).optional(),
31
45
  inactive: z.boolean({description: 'Agent is inactive'}).optional(),
@@ -40,6 +54,24 @@ export const agentConfigurationSchema = z.object({
40
54
  excludedLocations: z.array(z.string({description: 'Locations the agent is excluded from'})).optional(),
41
55
  model: z.enum(['Scout9', 'bard', 'openai']).optional().default('openai'),
42
56
  transcripts: z.array(z.array(MessageSchema)).optional(),
43
- audioRef: z.array(z.any()).optional()
57
+ audios: z.array(z.any()).optional()
58
+ });
59
+
60
+ /**
61
+ * @typedef {import('zod').infer<typeof agentBaseConfigurationSchema>} IAgent
62
+ * @typedef {import('zod').infer<typeof agentBaseConfigurationSchema>} IPersona
63
+ */
64
+ export const agentConfigurationSchema = agentBaseConfigurationSchema.extend({
65
+ id: zId('Agent ID', {description: 'Unique ID for agent'}),
44
66
  });
67
+
68
+ /**
69
+ * @typedef {import('zod').infer<typeof agentsConfigurationSchema>} IAgentsConfiguration
70
+ */
45
71
  export const agentsConfigurationSchema = z.array(agentConfigurationSchema);
72
+
73
+ /**
74
+ * @typedef {import('zod').infer<typeof agentsBaseConfigurationSchema>} IAgentsBaseConfiguration
75
+ */
76
+ export const agentsBaseConfigurationSchema = z.array(agentBaseConfigurationSchema);
77
+
@@ -1,11 +1,12 @@
1
- import { ZodString } from 'zod';
1
+ import { z } from 'zod';
2
2
 
3
- export function zId(name, strType) {
4
- if (strType instanceof ZodString) {
5
- return strType.regex(/^[A-Za-z0-9\-_\[\]]+$/, {
6
- message: `Invalid ${name} ID: ID must not contain spaces and should only contain alphanumeric characters, dashes, or underscores.`
7
- });
8
- } else {
9
- throw new Error(`Invalid zId type: ${strType}`);
10
- }
3
+ /**
4
+ * @param {string} name
5
+ * @param {Object} [props]
6
+ * @returns {import('zod').ZodString}
7
+ */
8
+ export function zId(name, props = {}) {
9
+ return z.string(props).regex(/^[A-Za-z0-9\-_\[\]]+$/, {
10
+ message: `Invalid ${name} ID: ID must not contain spaces and should only contain alphanumeric characters, dashes, or underscores.`
11
+ });
11
12
  }
@@ -2,11 +2,14 @@
2
2
 
3
3
  import { z } from 'zod';
4
4
  import { zId } from './utils.js';
5
- import { agentConfigurationSchema, customerSchema } from './agent.js';
5
+ import { agentConfigurationSchema, customerSchema } from './users.js';
6
6
  import { MessageSchema } from './message.js';
7
7
 
8
8
 
9
9
 
10
+ /**
11
+ * @typedef {import('zod').infer<typeof WorkflowConfigurationSchema>} IWorkflowConfiguration
12
+ */
10
13
  export const WorkflowConfigurationSchema = z.object({
11
14
  entities: z.array(zId('Workflow Folder', z.string()), {description: 'Workflow id association, used to handle route params'})
12
15
  .min(1, 'Must have at least 1 entity')
@@ -14,8 +17,15 @@ export const WorkflowConfigurationSchema = z.object({
14
17
  entity: zId('Workflow Folder', z.string()),
15
18
  });
16
19
 
20
+ /**
21
+ * @typedef {import('zod').infer<typeof WorkflowsConfigurationSchema>} IWorkflowsConfiguration
22
+ */
17
23
  export const WorkflowsConfigurationSchema = z.array(WorkflowConfigurationSchema);
18
24
 
25
+
26
+ /**
27
+ * @typedef {import('zod').infer<typeof ConversationSchema>} IConversation
28
+ */
19
29
  export const ConversationSchema = z.object({
20
30
  $agent: zId('Conversation Agent ID', z.string({description: 'Default agent assigned to the conversation(s)'})),
21
31
  $customer: zId('Conversation Customer ID', z.string({description: 'Customer this conversation is with'})),
@@ -25,22 +35,54 @@ export const ConversationSchema = z.object({
25
35
  subject: z.string({description: 'HTML Subject of the conversation'}).optional(),
26
36
  platformEmailThreadId: z.string({description: 'Used to sync email messages with the conversation'}).optional(),
27
37
  }).optional(),
38
+ locked: z.boolean({description: 'Whether the conversation is locked or not'}).optional().nullable(),
39
+ lockedReason: z.string({description: 'Why this conversation was locked'}).optional().nullable(),
40
+ lockAttempts: z.number({description: 'Number attempts made until conversation is locked'}).optional().nullable(),
41
+ forwardedTo: z.string({description: 'What personaId/phone/email was forwarded'}).optional().nullable(),
42
+ forwarded: z.string({description: 'Datetime ISO 8601 timestamp when persona was forwarded'}).optional().nullable(),
43
+ forwardNote: z.string().optional().nullable(),
44
+ intent: z.string({description: 'Detected intent of conversation'}).optional().nullable(),
45
+ intentScore: z.number({description: 'Confidence score of the assigned intent'}).optional().nullable(),
46
+ });
47
+
48
+ /**
49
+ * @typedef {import('zod').infer<typeof IntentWorkflowEventSchema>} IIntentWorkflowEvent
50
+ */
51
+ export const IntentWorkflowEventSchema = z.object({
52
+ current: z.string().nullable(),
53
+ flow: z.array(z.string()),
54
+ initial: z.string().nullable()
28
55
  });
29
56
 
30
- export const WorkflowEventSchema = {
57
+ /**
58
+ * @typedef {import('zod').infer<typeof WorkflowEventSchema>} IWorkflowEvent
59
+ */
60
+ export const WorkflowEventSchema = z.object({
31
61
  messages: z.array(MessageSchema),
32
62
  conversation: ConversationSchema,
33
63
  context: z.any(),
34
64
  message: MessageSchema,
35
- agent: agentConfigurationSchema,
65
+ agent: agentConfigurationSchema.omit({
66
+ transcripts: true,
67
+ audios: true,
68
+ includedLocations: true,
69
+ excludedLocations: true,
70
+ model: true,
71
+ context: true
72
+ }),
36
73
  customer: customerSchema,
37
- intent: z.string(),
38
- }
74
+ intent: IntentWorkflowEventSchema,
75
+ stagnationCount: z.number(),
76
+ note: z.string({description: 'Any developer notes to provide'}).optional()
77
+ })
39
78
 
40
79
  const Primitive = z.union([z.string(), z.number(), z.boolean()]);
41
80
  // Assuming ConversationContext is already defined as a Zod schema
42
81
 
43
- // Lazy is used to handle recursive types.
82
+ /**
83
+ * Lazy is used to handle recursive types.
84
+ * @typedef {import('zod').infer<typeof ConversationContext>} IConversation
85
+ */
44
86
  export const ConversationContext = z.lazy(() =>
45
87
  z.record(
46
88
  Primitive.or(ConversationContext)
@@ -49,32 +91,112 @@ export const ConversationContext = z.lazy(() =>
49
91
 
50
92
  const ContextSchema = z.record(Primitive.or(ConversationContext));
51
93
 
94
+ /**
95
+ * Forward input information of a conversation
96
+ * @typedef {import('zod').infer<typeof ForwardSchema>} IForward
97
+ */
52
98
  export const ForwardSchema = z.union([
53
99
  z.boolean(),
54
100
  z.string(),
55
101
  z.object({
56
102
  to: z.string().optional(),
57
103
  mode: z.enum(['after-reply', 'immediately']).optional(),
104
+ note: z.string({description: 'Note to provide to the agent'}).optional()
58
105
  }),
59
- ]);
106
+ ], {description: 'Forward input information of a conversation'});
107
+
60
108
 
109
+ /**
110
+ * Instruction object schema used to send context to guide conversations
111
+ * @typedef {import('zod').infer<typeof InstructionSchema>} IInstruction
112
+ */
61
113
  export const InstructionSchema = z.object({
62
- id: zId('Instruction ID', z.string()).description('Unique ID for the instruction, this is used to remove the instruction later'),
114
+ id: zId('Instruction ID').describe('Unique ID for the instruction, this is used to remove the instruction later'),
63
115
  content: z.string(),
64
116
  });
117
+
118
+ /**
119
+ * If its a string, it will be sent as a static string.
120
+ * If it's a object or WorkflowResponseMessageAPI - it will use
121
+ * @typedef {import('zod').infer<typeof WorkflowResponseMessage>} IWorkflowResponseMessage
122
+ */
123
+ export const WorkflowResponseMessage = z.union(
124
+ z.string(),
125
+
126
+ /**
127
+ * An api call that should be called later, must return a string or {message: string}
128
+ */
129
+ WorkflowResponseMessageApiRequest
130
+ );
131
+
132
+ /**
133
+ * @typedef {import('zod').infer<typeof WorkflowResponseMessageApiRequest>} IWorkflowResponseMessageApiRequest
134
+ */
135
+ export const WorkflowResponseMessageApiRequest = z.object({
136
+ uri: z.string(),
137
+ data: z.any().optional(),
138
+ headers: z.object({
139
+ [z.string()]: z.string(),
140
+ }).optional(),
141
+ method: z.enum(["GET", "POST", "PUT"]).optional()
142
+ });
143
+
144
+ /**
145
+ * The intended response provided by the WorkflowResponseMessageApiRequest
146
+ * @typedef {import('zod').infer<typeof WorkflowResponseMessageApiResponse>} IWorkflowResponseMessageApiResponse
147
+ */
148
+ export const WorkflowResponseMessageApiResponse = z.union([
149
+ z.string(),
150
+ z.object({
151
+ message: z.string()
152
+ }),
153
+ z.object({
154
+ text: z.string()
155
+ }),
156
+ z.object({
157
+ data: z.object({
158
+ message: z.string()
159
+ })
160
+ }),
161
+ z.object({
162
+ data: z.object({
163
+ text: z.string()
164
+ })
165
+ })
166
+ ]);
167
+
168
+ /**
169
+ * The workflow response object slot
170
+ * @typedef {import('zod').infer<typeof WorkflowResponseSlotSchema>} IWorkflowResponseSlot
171
+ */
65
172
  export const WorkflowResponseSlotSchema = z.object({
66
173
  forward: ForwardSchema.optional(),
174
+ forwardNote: z.string({description: 'Note to provide to the agent, recommend using forward object api instead'}).optional(),
67
175
  instructions: z.union([z.string(), InstructionSchema, z.array(z.string()), z.array(InstructionSchema)]).optional(),
68
176
  removeInstructions: z.array(z.string()).optional(),
69
177
  message: z.string().optional(),
178
+ // message: WorkflowResponseMessage.optional(),
70
179
  secondsDelay: z.number().optional(),
71
180
  scheduled: z.number().optional(),
72
181
  contextUpsert: ConversationContext.optional(),
73
182
  resetIntent: z.boolean().optional(),
74
183
  });
75
184
 
76
-
185
+ /**
186
+ * The workflow response to send in any given workflow
187
+ * @typedef {import('zod').infer<typeof WorkflowResponseSchema>} IWorkflowResponse
188
+ */
77
189
  export const WorkflowResponseSchema = z.union([
78
190
  WorkflowResponseSlotSchema,
79
191
  z.array(WorkflowResponseSlotSchema)
80
192
  ]);
193
+
194
+ /**
195
+ * @typedef {import('zod').infer<typeof WorkflowFunctionSchema>} IWorkflowFunction
196
+ */
197
+ export const WorkflowFunctionSchema = z.function()
198
+ .args(WorkflowEventSchema)
199
+ .returns(z.union([
200
+ z.promise(WorkflowResponseSchema),
201
+ WorkflowResponseSchema,
202
+ ]));
@@ -63,12 +63,12 @@ function customRequire(folder, moduleName) {
63
63
 
64
64
  export async function runInVM(
65
65
  event,
66
- {folder, filePath, fileName}
66
+ {src, filePath, fileName}
67
67
  ) {
68
68
  // Prepare the script context
69
69
  const scriptExports = {};
70
70
  const context = vm.createContext({
71
- require: (moduleName) => customRequire(folder, moduleName),
71
+ require: (moduleName) => customRequire(src, moduleName),
72
72
  console,
73
73
  module: { exports: scriptExports },
74
74
  exports: scriptExports,
@@ -0,0 +1,373 @@
1
+ import { Configuration, Scout9Api } from '@scout9/admin';
2
+ import {grey, italic, bgWhite, black} from 'kleur/colors';
3
+ import { createMockConversation, createMockWorkflowEvent } from './mocks.js';
4
+ import { loadConfig } from '../core/config/index.js';
5
+ import { requireProjectFile } from '../utils/index.js';
6
+ import { globSync } from 'glob';
7
+
8
+ export * from './spirits.js';
9
+
10
+ import { Spirits } from './spirits.js';
11
+
12
+ /**
13
+ * Testing tool kit, used to handle Scout9 operations such as parsing, workflow, and generating responses
14
+ */
15
+ export class Scout9Test {
16
+
17
+ /**
18
+ * @type {import('../runtime/client/users.js').ICustomer}
19
+ */
20
+ customer;
21
+
22
+ /**
23
+ * @type {import('../runtime/client/users.js').IPersona}
24
+ */
25
+ persona;
26
+
27
+ /**
28
+ * @type {import('../runtime/client/workflow.js').IConversation}
29
+ */
30
+ conversation;
31
+
32
+ /**
33
+ * @type {import('../runtime/client/message.js').IMessage[]}
34
+ */
35
+ messages;
36
+
37
+ /**
38
+ * @type {any}
39
+ */
40
+ context;
41
+
42
+ /**
43
+ * @private
44
+ * @type {import('../runtime/client/config.js').IScout9ProjectBuildConfig | null}
45
+ */
46
+ _project = null;
47
+
48
+ /**
49
+ * @private
50
+ * @type {import('../runtime/client/workflow.js').IWorkflowFunction | null}
51
+ */
52
+ _app = null;
53
+
54
+ /**
55
+ * @private
56
+ * @type {import('@scout9/admin').Scout9Api | null}
57
+ */
58
+ _api = null;
59
+
60
+ /**
61
+ * @private
62
+ */
63
+ _cwd;
64
+
65
+ /**
66
+ * @private
67
+ */
68
+ _src;
69
+
70
+ /**
71
+ * @private
72
+ */
73
+ _mode;
74
+
75
+ /**
76
+ * @private
77
+ */
78
+ _loaded;
79
+
80
+ /**
81
+ * @private
82
+ */
83
+ _personaId;
84
+
85
+
86
+ /**
87
+ * Mimics a customer message to your app (useful for testing)
88
+ * @param props - the Scout9Test properties
89
+ * @param {import('../runtime/client/users.js').ICustomer | undefined} [props.customer] - customer to use
90
+ * @param {any | undefined} [props.context] - prior conversation context
91
+ * @param {string | undefined} [props.persona] id to use
92
+ * @param {import('../runtime/client/workflow.js').IConversation | undefined} [props.conversation] - existing conversation
93
+ * @param {string | undefined} [props.cwd]
94
+ * @param {string | undefined} [props.src]
95
+ * @param {string | undefined} [props.mode]
96
+ * @param {import('@scout9/admin').Scout9Api} [props.api]
97
+ * @param {import('../runtime/client/workflow.js').IWorkflowFunction} [props.app]
98
+ * @param {import('../runtime/client/config.js').IScout9ProjectBuildConfig} [props.project]
99
+ */
100
+ constructor(
101
+ {
102
+ persona,
103
+ customer,
104
+ context,
105
+ conversation = createMockConversation(),
106
+ cwd = process.cwd(),
107
+ src = 'src',
108
+ mode = 'production',
109
+ api,
110
+ app,
111
+ project
112
+ } = {
113
+ cwd: process.cwd(),
114
+ src: 'src',
115
+ mode: 'production'
116
+ }
117
+ ) {
118
+ this.messages = [];
119
+ this._cwd = cwd;
120
+ this._src = src;
121
+ this._mode = mode;
122
+ this.context = context || {};
123
+ this.conversation = conversation;
124
+ if (api) {
125
+ this._api = api;
126
+ }
127
+ if (app) {
128
+ this._app = app;
129
+ }
130
+ if (project) {
131
+ this._project = project;
132
+ }
133
+ if (!customer) {
134
+ customer = {
135
+ id: 'mock_customer_' + Math.random().toString(36).slice(2, 11),
136
+ name: 'Mock Customer',
137
+ firstName: 'Mock',
138
+ lastName: 'Customer'
139
+ };
140
+ this.conversation.$customer = customer.id;
141
+ } else {
142
+ this.conversation.$customer = customer.id;
143
+ }
144
+ this.customer = customer;
145
+ this.context.customer = customer;
146
+ this._personaId = persona;
147
+ }
148
+
149
+ /**
150
+ * Loads the test environment
151
+ * @param {boolean} [override] - defaults to false, if true, it will override the current loaded state such as the scout9 api, workflow function, and project config
152
+ * @returns {Promise<void>}
153
+ */
154
+ async load(override = false) {
155
+
156
+ // Load app (if not already loaded or override true)
157
+ if (override || !this._app) {
158
+ this._app = await this._loadApp();
159
+ }
160
+
161
+ // Load app configuration (if not already loaded or override true)
162
+ if (override || !this._project) {
163
+ this._project = await loadConfig({cwd: this._cwd, src: this._src, mode: this._mode})
164
+ }
165
+
166
+ if (override || !this._api) {
167
+ this._api = new Scout9Api(new Configuration({apiKey: process.env.SCOUT9_API_KEY}));
168
+ }
169
+
170
+ if (!this._personaId) {
171
+ this._personaId = (this._project.persona || this._project.agents)?.[0]?.id;
172
+ if (!this._personaId) {
173
+ throw new Error(`No persona found in config, please specify a persona id`);
174
+ }
175
+ }
176
+ this.conversation.$agent = this._personaId;
177
+ this.persona = (this._project.persona || this._project.agents).find(p => p.id === this._personaId);
178
+ if (!this.persona) {
179
+ throw new Error(`Could not find persona with id: ${this._personaId}, ensure your project is sync'd by running "scout9 sync" or you are using the correct persona id`);
180
+ }
181
+ this.context.agent = this.persona;
182
+ this._loaded = true;
183
+ }
184
+
185
+ /**
186
+ * Teardown the test environment
187
+ */
188
+ teardown() {
189
+ this._loaded = false;
190
+ this._api = null;
191
+ this._project = null;
192
+ this._app = null;
193
+ }
194
+
195
+ /**
196
+ * Send a message as a customer to your app
197
+ * @param {string} message - message to send
198
+ * @param {import('@scout9/app/testing-tools').StatusCallback | boolean} [progress] - progress callback, if true, will log progress, can override with your own callback. If not provided, no logs will be added.
199
+ * @returns {Promise<ConversationEvent>}
200
+ */
201
+ async send(message, progress = false) {
202
+ if (!this._loaded) {
203
+ await this.load();
204
+ }
205
+
206
+ const defaultProgressLogger = (message, level = 'info', type = '') => {
207
+ const typeStdout = type ? italic(bgWhite(' ' + black(type) + ' ')) : '';
208
+ const messageStdout = grey(message);
209
+ (console.hasOwnProperty(level) ? console[level] : console.log)(`\t${typeStdout ? typeStdout + ' ' : ''}${messageStdout}`);
210
+ }
211
+
212
+ // If custom logger provided, use it, otherwise use default logger
213
+ let progressInput = typeof progress === 'function' ? progress : defaultProgressLogger;
214
+
215
+ // If progress turned off, use a no-op function
216
+ if (typeof progress === 'boolean') {
217
+ if (!!progress) {
218
+ progressInput = defaultProgressLogger; // use default logger
219
+ } else {
220
+ progressInput = () => {}; // use no-op
221
+ }
222
+ }
223
+
224
+ const _message = {
225
+ id: 'user_mock_' + Math.random().toString(36).slice(2, 11),
226
+ role: 'customer',
227
+ content: message,
228
+ time: new Date().toISOString()
229
+ };
230
+ this.messages.push(_message);
231
+ const result = await Spirits.customer({
232
+ customer: this.customer,
233
+ config: this._project,
234
+ parser: async (_msg, _lng) => {
235
+ // @TODO can't do this for HUGE data sets
236
+ const detectableEntities = this._project.entities.filter(e => e.training?.length > 0 && e.definitions?.length > 0);
237
+ return this._api.parse({
238
+ message: _msg,
239
+ language: _lng,
240
+ entities: detectableEntities
241
+ }).then((_res => _res.data));
242
+ },
243
+ workflow: async (event) => {
244
+ return this._app(event);
245
+ },
246
+ generator: (request) => {
247
+ return this._api.generate(request).then((_res => _res.data));
248
+ },
249
+ idGenerator: (prefix) => prefix + '_' + Math.random().toString(36).slice(2, 11),
250
+ progress: progressInput,
251
+ message: _message,
252
+ context: this.context,
253
+ messages: this.messages,
254
+ conversation: this.conversation
255
+ });
256
+
257
+ this.context = result.context.after;
258
+ this.messages = result.messages.after;
259
+ this.conversation = result.conversation.after;
260
+
261
+ if (!!result.conversation.forward) {
262
+ // @TODO migrate this
263
+ if (typeof result.conversation.forward === 'string') {
264
+ this.conversation.forwardedTo = result.conversation.forward;
265
+ } else if (result.conversation.forward === true) {
266
+ this.conversation.forwardedTo = this.persona.forwardPhone || this.persona.forwardEmail || 'No Forward';
267
+ } else if (!!result.conversation.forward?.to) {
268
+ this.conversation.forwardedTo = result.conversation.forward.to;
269
+ } else {
270
+ console.error(`Invalid forward result`, result.conversation.forward);
271
+ this.conversation.forwardedTo = 'Invalid Forward';
272
+ }
273
+ this.conversation.forwarded = new Date().toString();
274
+ this.conversation.forwardNote = result.conversation.forwardNote || '';
275
+ this.conversation.locked = true;
276
+ this.conversation.lockedReason = result.conversation.forwardNote ?? ('Forwarded to ' + this.conversation.forwardedTo);
277
+ }
278
+
279
+ if (!result.messages.after.find(m => m.id === result.message.after.id)) {
280
+ console.error(`Message not found in result.messages.after`, result.message.after.id);
281
+ }
282
+
283
+ return result;
284
+ }
285
+
286
+ /**
287
+ * Parse user message
288
+ * @param {string} message - message string to parse
289
+ * @param {string} [language] - language to parse in, defaults to "en" for english
290
+ * @returns {Promise<import('@scout9/admin').ParseResponse>}
291
+ */
292
+ async parse(message, language = 'en') {
293
+ if (!this._project) {
294
+ throw new Error(`Config is not defined`);
295
+ }
296
+ return this._api.parse({
297
+ message,
298
+ language,
299
+ entities: this._project.entities
300
+ }).then((_res => _res.data));
301
+ }
302
+
303
+ /**
304
+ * Runs your local app workflow
305
+ * @param {string} message - the message to run through the workflow
306
+ * @param {Omit<Partial<import('../runtime/client/workflow.js').IWorkflowEvent>, 'message'> | undefined} [event] - additional event data
307
+ * @returns {Promise<import('../runtime/client/workflow.js').IWorkflowResponse>}
308
+ */
309
+ async workflow(message, event = {}) {
310
+ if (!this._app) {
311
+ throw new Error(`Workflow function is not loaded or found - make sure to run ".load()" before calling ".workflow()"`);
312
+ }
313
+ if (event.hasOwnProperty('message')) {
314
+ console.warn(`WARNING: inserting a "event.message" will overwrite your "message" argument`);
315
+ }
316
+ return this._app({
317
+ ...createMockWorkflowEvent(message),
318
+ ...event
319
+ });
320
+ }
321
+
322
+ /**
323
+ * Generate a response to the user from the given or registered persona's voice in relation to the current conversation's context.
324
+ * @param {Object} [input] - Generation input, defaults to test registered data such as existing messages, context, and persona information.
325
+ * @param {string} [input.personaId] - Persona ID to use, defaults to test registered persona id.
326
+ * @param {Partial<import('@scout9/admin').ConversationCreateRequest>} [input.conversation] - Conversation overrides, defaults to test registered conversation data.
327
+ * @param {import('../runtime/client/message.js').IMessage[]} [input.messages] - Message overrides, defaults to test registered message data.
328
+ * @param {any} [input.context] - Context overrides, defaults to test registered context data.
329
+ * @returns {Promise<import('@scout9/admin').GenerateResponse>}
330
+ */
331
+ async generate({personaId = this._personaId, conversation = {}, messages = this.messages, context = this.context}) {
332
+ if (!this._api) {
333
+ throw new Error(`Scout9 API is not loaded or found - make sure to run ".load()" before calling ".generate()"`);
334
+ }
335
+ if (!this._project) {
336
+ throw new Error(`Config is not defined - make sure to run ".load()" before calling ".generate()"`);
337
+ }
338
+ const persona = (this._project.persona || this._project.agents).find(p => p.id === personaId);
339
+ if (!persona) {
340
+ throw new Error(`Could not find persona with id: ${personaId}, ensure your project is sync'd by running "scout9 sync"`);
341
+ }
342
+ return this._api.generate({
343
+ convo: {
344
+ $customer: this.customer.id,
345
+ environment: this.conversation.environment,
346
+ initialContexts: this.conversation.initialContexts || [],
347
+ ...conversation,
348
+ $agent: persona
349
+ },
350
+ messages,
351
+ context,
352
+ persona,
353
+ llm: this._project.llm,
354
+ pmt: this._project.pmt
355
+ }).then((_res => _res.data));
356
+ }
357
+
358
+ /**
359
+ * @private
360
+ */
361
+ async _loadApp() {
362
+ const paths = globSync(`${this._src}/app.{ts,cjs,mjs,js}`, {cwd: this._cwd, absolute: true});
363
+ if (paths.length === 0) {
364
+ throw new Error(`Missing main project entry file ${this._src}/app.{js|ts|cjs|mjs}`);
365
+ } else if (paths.length > 1) {
366
+ throw new Error(`Multiple main project entry files found ${this._src}/app.{js|ts|cjs|mjs}`);
367
+ }
368
+ const [appFilePath] = paths;
369
+ return requireProjectFile(appFilePath)
370
+ .then(mod => mod.default);
371
+ }
372
+
373
+ }
@@ -1 +1,2 @@
1
1
  export * from './mocks.js';
2
+ export * from './dev.js';