@scout9/app 1.0.0-alpha.0.5.3 → 1.0.0-alpha.0.5.4

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.
@@ -3,7 +3,7 @@ import fs from 'node:fs/promises';
3
3
  import colors from 'kleur';
4
4
  import { globSync } from 'glob';
5
5
  import { Configuration, Scout9Api } from '@scout9/admin';
6
- import { checkVariableType, requireProjectFile } from '../../utils/index.js';
6
+ import { checkVariableType, requireProjectFile, simplifyError } from '../../utils/index.js';
7
7
  import { AgentsSchema, AgentsConfigurationSchema } from '../../runtime/index.js';
8
8
  import { audioExtensions } from '../../utils/audio-type.js';
9
9
  import { fileTypeFromBuffer } from '../../utils/file-type.js';
@@ -315,8 +315,7 @@ export default async function loadAgentConfig({
315
315
 
316
316
  const result = (deploying ? AgentsConfigurationSchema : AgentsSchema).safeParse(agents);
317
317
  if (!result.success) {
318
- result.error.source = paths[0];
319
- throw result.error;
318
+ throw simplifyError(result.error);
320
319
  }
321
320
 
322
321
  if (serverDeployed) {
@@ -1,41 +1,51 @@
1
1
  import { globSync } from 'glob';
2
- import { CommandsSchema } from '../../runtime/index.js';
2
+ import { CommandSchema, CommandsSchema } from '../../runtime/index.js';
3
3
  import { basename, dirname } from 'node:path';
4
+ import { simplifyError } from '../../utils/index.js';
5
+ import { logUserValidationError } from '../../report.js';
4
6
 
5
7
 
6
8
  /**
7
9
  * @returns {Promise<CommandConfiguration[]>}
8
10
  */
9
11
  export default async function loadCommandsConfig(
10
- {
11
- cwd = process.cwd(),
12
- src = 'src',
13
- cb = (message) => {}
14
- } = {}
12
+ {
13
+ cwd = process.cwd(),
14
+ src = 'src',
15
+ cb = (message) => {
16
+ }
17
+ } = {}
15
18
  ) {
16
- // const config = globSync(path.resolve(cwd, `${src}/workflows/**/workflow.{ts,js}`), {cwd, absolute: true})
17
- const config = globSync(`${src}/commands/**/{index,command,*.command}.{ts,js}`, {cwd, absolute: false})
18
- .map((path) => {
19
+ // const config = globSync(path.resolve(cwd, `${src}/workflows/**/workflow.{ts,js}`), {cwd, absolute: true})
20
+ const config = globSync(`${src}/commands/**/{index,command,*.command}.{ts,js}`, {cwd, absolute: false})
21
+ .map((path) => {
19
22
 
20
- const dir = dirname(path);
21
- let entity = basename(path).replace(/\..*$/, ''); // Removes any "."'s
22
- if (!entity) {
23
- throw new Error(`Invalid command path "${path}"`)
24
- }
25
- switch (entity) {
26
- case "index":
27
- case "command":
28
- entity = basename(dir);
29
- break;
30
- }
31
- return {
32
- entity,
33
- path: path.split(src + '/commands/')[1],
34
- }
35
- });
23
+ const dir = dirname(path);
24
+ let entity = basename(path).replace(/\..*$/, ''); // Removes any "."'s
25
+ if (!entity) {
26
+ throw new Error(`Invalid command path "${path}"`);
27
+ }
28
+ switch (entity) {
29
+ case 'index':
30
+ case 'command':
31
+ entity = basename(dir);
32
+ break;
33
+ }
34
+ try {
35
+ return CommandSchema.parse({
36
+ entity,
37
+ path: path.split(src + '/commands/')[1]
38
+ });
39
+ } catch (e) {
40
+ throw logUserValidationError(e, path);
41
+ }
42
+ });
36
43
 
37
- // Validate the config
38
- CommandsSchema.parse(config);
44
+ // Validate the config
45
+ try {
46
+ return CommandsSchema.parse(config);
47
+ } catch (e) {
48
+ throw simplifyError(e);
49
+ }
39
50
 
40
- return config;
41
51
  }
@@ -6,7 +6,7 @@ import {
6
6
  EntityConfigurationSchema,
7
7
  EntityRootProjectConfigurationSchema
8
8
  } from '../../runtime/index.js';
9
- import { checkVariableType, requireOptionalProjectFile, requireProjectFile } from '../../utils/index.js';
9
+ import { checkVariableType, requireOptionalProjectFile, requireProjectFile, simplifyError } from '../../utils/index.js';
10
10
  import { logUserValidationError } from '../../report.js';
11
11
 
12
12
  async function loadEntityApiConfig(cwd, filePath) {
@@ -23,8 +23,11 @@ async function loadEntityApiConfig(cwd, filePath) {
23
23
  config[key] = true;
24
24
  }
25
25
  }
26
- EntityApiConfigurationSchema.parse(config);
27
- return config;
26
+ try {
27
+ return EntityApiConfigurationSchema.parse(config);
28
+ } catch (e) {
29
+ throw simplifyError(config);
30
+ }
28
31
  } else {
29
32
  return null;
30
33
  }
@@ -81,8 +84,7 @@ export default async function loadEntitiesConfig(
81
84
  // Validate entity configuration
82
85
  const result = EntityConfigurationSchema.safeParse(entityConfig, {path: ['entities', config.length]});
83
86
  if (!result.success) {
84
- logUserValidationError(result.error, filePath);
85
- throw result.error;
87
+ throw logUserValidationError(result.error, filePath);
86
88
  }
87
89
  } else if (isSpecial && (fileName === 'index' || fileName === 'config')) {
88
90
  // If this is a special entity file, then ignore as we will capture it another method
@@ -96,7 +98,11 @@ export default async function loadEntitiesConfig(
96
98
  entities: parents.reverse(),
97
99
  api
98
100
  };
99
- EntityRootProjectConfigurationSchema.parse(entityProjectConfig);
101
+ try {
102
+ EntityRootProjectConfigurationSchema.parse(entityProjectConfig);
103
+ } catch (e) {
104
+ throw simplifyError(e);
105
+ }
100
106
  const existingIndex = config.findIndex(c => c.entity === entityProjectConfig.entity);
101
107
  if (existingIndex > -1) {
102
108
  if (config[existingIndex].entities.length !== entityProjectConfig.entities.length) {
@@ -134,8 +140,10 @@ export default async function loadEntitiesConfig(
134
140
  }
135
141
 
136
142
  // Validate the config
137
- EntitiesRootProjectConfigurationSchema.parse(config);
138
-
139
- return config;
143
+ try {
144
+ return EntitiesRootProjectConfigurationSchema.parse(config);
145
+ } catch (e) {
146
+ throw simplifyError(e);
147
+ }
140
148
  }
141
149
 
@@ -6,8 +6,9 @@ import loadEntitiesConfig from './entities.js';
6
6
  import loadProjectConfig from './project.js';
7
7
  import loadWorkflowsConfig from './workflow.js';
8
8
  import { Scout9ProjectBuildConfigSchema } from '../../runtime/index.js';
9
- import { ProgressLogger } from '../../utils/index.js';
9
+ import { ProgressLogger, simplifyError } from '../../utils/index.js';
10
10
  import loadCommandsConfig from './commands.js';
11
+ import { logUserValidationError } from '../../report.js';
11
12
 
12
13
 
13
14
  export function loadEnvConfig({
@@ -65,7 +66,7 @@ export async function loadConfig({
65
66
  const result = Scout9ProjectBuildConfigSchema.safeParse(projectConfig);
66
67
  if (!result.success) {
67
68
  result.error.source = `${src}/index.js`;
68
- throw result.error;
69
+ throw logUserValidationError(result.error, `${src}/index.js`);
69
70
  }
70
71
 
71
72
  return projectConfig;
@@ -1,5 +1,6 @@
1
1
  import { globSync } from 'glob';
2
2
  import { WorkflowConfigurationSchema, WorkflowsConfigurationSchema } from '../../runtime/index.js';
3
+ import { logUserValidationError } from '../../report.js';
3
4
 
4
5
 
5
6
  /**
@@ -35,13 +36,18 @@ export default async function loadWorkflowsConfig(
35
36
  entity: parents[0],
36
37
  entities: parents.reverse(),
37
38
  }
38
- WorkflowConfigurationSchema.parse(workflowConfig);
39
-
40
- return workflowConfig;
39
+ try {
40
+ return WorkflowConfigurationSchema.parse(workflowConfig);
41
+ } catch (e) {
42
+ throw logUserValidationError(e, path);
43
+ }
41
44
  });
42
45
 
43
46
  // Validate the config
44
- WorkflowsConfigurationSchema.parse(config);
47
+ try {
48
+ return WorkflowsConfigurationSchema.parse(config);
49
+ } catch (e) {
50
+ throw logUserValidationError(e, src);
51
+ }
45
52
 
46
- return config;
47
53
  }
package/src/core/index.js CHANGED
@@ -6,7 +6,7 @@ import path from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import fetch, { FormData } from 'node-fetch';
8
8
  import { Configuration, Scout9Api } from '@scout9/admin';
9
- import { checkVariableType, ProgressLogger, requireProjectFile } from '../utils/index.js';
9
+ import { checkVariableType, ProgressLogger, requireProjectFile, simplifyError } from '../utils/index.js';
10
10
  import decompress from 'decompress';
11
11
  import { loadUserPackageJson } from './config/project.js';
12
12
  import { platformApi } from './data.js';
@@ -300,8 +300,7 @@ export async function getAgentContacts() {
300
300
  export async function run(event, {eventSource} = {}) {
301
301
  const result = WorkflowEventSchema.safeParse(event);
302
302
  if (!result.success) {
303
- logUserValidationError(result.error, eventSource);
304
- throw result.error;
303
+ throw logUserValidationError(result.error, eventSource);
305
304
  }
306
305
  const configuration = new Configuration({
307
306
  apiKey: process.env.SCOUT9_API_KEY
package/src/report.js CHANGED
@@ -1,16 +1,17 @@
1
1
  import { red, cyan, green, dim, white, grey, bold, italic, magenta, yellow } from 'kleur/colors';
2
- import { ProgressLogger } from './utils/index.js';
2
+ import { ProgressLogger, simplifyError } from './utils/index.js';
3
3
 
4
4
  /**
5
5
  *
6
6
  * @param {import('zod').ZodError} zodError
7
7
  * @param {string} source
8
+ * @returns {import('zod-validation-error').ValidationError}
8
9
  */
9
10
  export function logUserValidationError(
10
11
  zodError,
11
12
  source,
12
13
  ) {
13
- const issues = zodError.issues || [];
14
+ const issues = zodError?.issues || [];
14
15
 
15
16
  console.log(red(`Scout9 Schema Validation Error at ${bold(source)}, fix these issues and rerun`));
16
17
  for (const {code, expected, received, path, message} of issues) {
@@ -29,6 +30,11 @@ export function logUserValidationError(
29
30
  }
30
31
  console.log('\t', objectPath, grey(`${text}`));
31
32
  }
33
+
34
+ const simplifiedError = simplifyError(zodError);
35
+ console.error(`\n${red(simplifiedError.message)}\n`);
36
+
37
+ return simplifiedError;
32
38
  }
33
39
 
34
40
 
@@ -1,47 +1,52 @@
1
1
  import { z } from 'zod';
2
2
  import { zId } from './utils.js';
3
+ import { CommandsSchema } from './workflow.js';
3
4
 
4
5
  export const ConversationContext = z.record(
5
- z.union([
6
- z.any(),
7
- z.string(),
8
- z.number(),
9
- z.boolean(),
10
- z.null(),
11
- z.array(
12
- z.union([z.string(), z.number(), z.boolean(), z.null()])
13
- )
14
- ])
6
+ z.union([
7
+ z.any(),
8
+ z.string(),
9
+ z.number(),
10
+ z.boolean(),
11
+ z.null(),
12
+ z.array(
13
+ z.union([z.string(), z.number(), z.boolean(), z.null()])
14
+ )
15
+ ])
15
16
  );
16
17
 
17
18
  export const ConversationAnticipateSchema = z.object({
18
- type: z.enum(['did', 'literal', 'context'], {description: "Determines the runtime to address the next response"}),
19
- slots: z.record(z.string(), z.array(z.any())),
20
- did: z.string({description: 'For type \'did\''}).optional(),
21
- map: z.array(z.object({
22
- slot: z.string(),
23
- keywords: z.array(z.string())
24
- }), {description: 'For literal keywords, this map helps point which slot the keyword matches to'}).optional()
19
+ type: z.enum(['did', 'literal', 'context'], {description: 'Determines the runtime to address the next response'}),
20
+ slots: z.record(z.string(), z.array(z.any())),
21
+ did: z.string({description: 'For type \'did\''}).optional(),
22
+ map: z.array(z.object({
23
+ slot: z.string(),
24
+ keywords: z.array(z.string())
25
+ }), {description: 'For literal keywords, this map helps point which slot the keyword matches to'}).optional()
25
26
  });
26
27
 
27
28
  export const ConversationSchema = z.object({
28
- $id: zId('Conversation ID', z.string({description: 'Conversation unique id'})),
29
- $agent: zId('Conversation Agent ID', z.string({description: 'Default agent assigned to the conversation(s)'})),
30
- $customer: zId('Conversation Customer ID', z.string({description: 'Customer this conversation is with'})),
31
- initialContexts: z.array(z.string(), {description: 'Initial contexts to load when starting the conversation'})
32
- .optional(),
33
- environment: z.enum(['phone', 'email', 'web']),
34
- environmentProps: z.object({
35
- subject: z.string({description: 'HTML Subject of the conversation'}).optional(),
36
- platformEmailThreadId: z.string({description: 'Used to sync email messages with the conversation'}).optional()
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
- anticipate: ConversationAnticipateSchema.optional()
29
+ $id: zId('Conversation ID', z.string({description: 'Conversation unique id'})),
30
+ $agent: zId('Conversation Agent ID', z.string({description: 'Default agent assigned to the conversation(s)'})),
31
+ $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'})
33
+ .optional(),
34
+ environment: z.enum(['phone', 'email', 'web']),
35
+ environmentProps: z.object({
36
+ subject: z.string({description: 'HTML Subject of the conversation'}).optional(),
37
+ platformEmailThreadId: z.string({description: 'Used to sync email messages with the conversation'}).optional()
38
+ }).optional(),
39
+ locked: z.boolean({description: 'Whether the conversation is locked or not'}).optional().nullable(),
40
+ lockedReason: z.string({description: 'Why this conversation was locked'}).optional().nullable(),
41
+ lockAttempts: z.number({description: 'Number attempts made until conversation is locked'}).optional().nullable(),
42
+ forwardedTo: z.string({description: 'What personaId/phone/email was forwarded'}).optional().nullable(),
43
+ forwarded: z.string({description: 'Datetime ISO 8601 timestamp when persona was forwarded'}).optional().nullable(),
44
+ forwardNote: z.string().optional().nullable(),
45
+ intent: z.string({description: 'Detected intent of conversation'}).optional().nullable(),
46
+ intentScore: z.number({description: 'Confidence score of the assigned intent'}).optional().nullable(),
47
+ anticipate: ConversationAnticipateSchema.optional(),
48
+ /**
49
+ * Whether this conversation is part of a command flow
50
+ */
51
+ command: CommandsSchema.optional()
47
52
  });