@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.
- package/README.md +32 -0
- package/dist/{index-92deaa5f.cjs → exports-e7d51b70.cjs} +46618 -4591
- package/dist/index.cjs +58 -15
- package/dist/{multipart-parser-090f08a9.cjs → multipart-parser-e09a67c9.cjs} +13 -7
- package/dist/spirits-3b603262.cjs +1218 -0
- package/dist/spirits.cjs +9 -0
- package/dist/testing-tools.cjs +48 -0
- package/package.json +30 -8
- package/src/cli.js +162 -69
- package/src/core/config/agents.js +300 -7
- package/src/core/config/entities.js +58 -28
- package/src/core/config/index.js +37 -15
- package/src/core/config/project.js +160 -6
- package/src/core/config/workflow.js +13 -12
- package/src/core/data.js +27 -0
- package/src/core/index.js +386 -137
- package/src/core/sync.js +71 -0
- package/src/core/templates/Dockerfile +22 -0
- package/src/core/templates/app.js +453 -0
- package/src/core/templates/project-files.js +36 -0
- package/src/core/templates/template-package.json +13 -0
- package/src/exports.js +21 -17
- package/src/platform.js +189 -33
- package/src/public.d.ts.text +330 -0
- package/src/report.js +117 -0
- package/src/runtime/client/api.js +56 -159
- package/src/runtime/client/config.js +60 -11
- package/src/runtime/client/entity.js +19 -6
- package/src/runtime/client/index.js +5 -3
- package/src/runtime/client/message.js +13 -3
- package/src/runtime/client/platform.js +86 -0
- package/src/runtime/client/{agent.js → users.js} +35 -3
- package/src/runtime/client/utils.js +10 -9
- package/src/runtime/client/workflow.js +131 -9
- package/src/runtime/entry.js +2 -2
- package/src/testing-tools/dev.js +373 -0
- package/src/testing-tools/index.js +1 -0
- package/src/testing-tools/mocks.js +37 -5
- package/src/testing-tools/spirits.js +530 -0
- package/src/utils/audio-buffer.js +16 -0
- package/src/utils/audio-type.js +27 -0
- package/src/utils/configs/agents.js +68 -0
- package/src/utils/configs/entities.js +145 -0
- package/src/utils/configs/project.js +23 -0
- package/src/utils/configs/workflow.js +47 -0
- package/src/utils/file-type.js +569 -0
- package/src/utils/file.js +158 -0
- package/src/utils/glob.js +30 -0
- package/src/utils/image-buffer.js +23 -0
- package/src/utils/image-type.js +39 -0
- package/src/utils/index.js +1 -0
- package/src/utils/is-svg.js +37 -0
- package/src/utils/logger.js +111 -0
- package/src/utils/module.js +14 -25
- package/src/utils/project-templates.js +191 -0
- package/src/utils/project.js +387 -0
- package/src/utils/video-type.js +29 -0
- package/types/index.d.ts +7588 -206
- package/types/index.d.ts.map +97 -22
- package/dist/index-1b8d7dd2.cjs +0 -49555
- package/dist/index-2ccb115e.cjs +0 -49514
- package/dist/index-66b06a30.cjs +0 -49549
- package/dist/index-bc029a1d.cjs +0 -49528
- package/dist/index-d9a93523.cjs +0 -49527
- package/dist/multipart-parser-1508046a.cjs +0 -413
- package/dist/multipart-parser-7007403a.cjs +0 -413
- package/dist/multipart-parser-70c32c1d.cjs +0 -413
- package/dist/multipart-parser-71dec101.cjs +0 -413
- package/dist/multipart-parser-f15bf2e0.cjs +0 -414
- package/src/public.d.ts +0 -209
package/src/core/sync.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { platformApi } from './data.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {import('../runtime/client/config.js').IScout9ProjectBuildConfig} config
|
|
5
|
+
* @returns {Promise<import('../runtime/client/config.js').IScout9ProjectBuildConfig>}
|
|
6
|
+
*/
|
|
7
|
+
export async function syncData(config) {
|
|
8
|
+
if (!process.env.SCOUT9_API_KEY) {
|
|
9
|
+
throw new Error('Missing required environment variable "SCOUT9_API_KEY"');
|
|
10
|
+
}
|
|
11
|
+
const result = await platformApi(`https://scout9.com/api/b/platform/sync`).then((res) => {
|
|
12
|
+
if (res.status !== 200) {
|
|
13
|
+
throw new Error(`Server responded with ${res.status}: ${res.statusText}`);
|
|
14
|
+
}
|
|
15
|
+
return res.json();
|
|
16
|
+
})
|
|
17
|
+
.catch((err) => {
|
|
18
|
+
err.message = `Error fetching entities and agents: ${err.message}`;
|
|
19
|
+
throw err;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const { agents, entities, organization, initialContext } = result;
|
|
23
|
+
|
|
24
|
+
// Merge
|
|
25
|
+
config.agents = agents.reduce((accumulator, agent) => {
|
|
26
|
+
// Check if agent already exists
|
|
27
|
+
const existingAgentIndex = accumulator.findIndex(a => a.id === agent.id);
|
|
28
|
+
if (existingAgentIndex === -1) {
|
|
29
|
+
accumulator.push(agent);
|
|
30
|
+
} else {
|
|
31
|
+
// Merge agent
|
|
32
|
+
accumulator[existingAgentIndex] = {
|
|
33
|
+
...accumulator[existingAgentIndex],
|
|
34
|
+
...agent
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return accumulator;
|
|
38
|
+
}, config.agents);
|
|
39
|
+
|
|
40
|
+
// Remove agents that are not on the server
|
|
41
|
+
config.agents = config.agents.filter(agent => agents.find(a => a.id === agent.id));
|
|
42
|
+
|
|
43
|
+
// Merge entities
|
|
44
|
+
config.entities = entities.reduce((accumulator, entity) => {
|
|
45
|
+
// Check if agent already exists
|
|
46
|
+
const existingEntityIndex = accumulator.findIndex(a => a.id === entity.id);
|
|
47
|
+
if (existingEntityIndex === -1) {
|
|
48
|
+
accumulator.push(entity);
|
|
49
|
+
} else {
|
|
50
|
+
// Merge agent
|
|
51
|
+
accumulator[existingEntityIndex] = {
|
|
52
|
+
...accumulator[existingEntityIndex],
|
|
53
|
+
...entity
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return accumulator;
|
|
57
|
+
}, config.entities);
|
|
58
|
+
|
|
59
|
+
// Remove entities that are not on the server
|
|
60
|
+
config.entities = config.entities.filter(entity => entities.find(a => a.id === entity.id));
|
|
61
|
+
config.organization = {
|
|
62
|
+
...(config?.organization || {}),
|
|
63
|
+
...(organization || {})
|
|
64
|
+
};
|
|
65
|
+
config.initialContext = [
|
|
66
|
+
...(Array.isArray(config?.initialContext) ? config.initialContext : []),
|
|
67
|
+
...(Array.isArray(initialContext) ? initialContext : [])
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
return config;
|
|
71
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Use Node.js version 16
|
|
2
|
+
FROM node:18-slim
|
|
3
|
+
|
|
4
|
+
# Create and change to the app directory.
|
|
5
|
+
WORKDIR /usr/src/app
|
|
6
|
+
|
|
7
|
+
# Copy application dependency manifests to the container image.
|
|
8
|
+
# A wildcard is used to ensure both package.json AND package-lock.json are copied.
|
|
9
|
+
# Copying this separately prevents re-running npm install on every code change.
|
|
10
|
+
COPY package*.json ./
|
|
11
|
+
|
|
12
|
+
# Install production dependencies.
|
|
13
|
+
RUN npm install --only=production
|
|
14
|
+
|
|
15
|
+
# Copy local code to the container image.
|
|
16
|
+
COPY . ./
|
|
17
|
+
|
|
18
|
+
# Expose port for Cloud Run
|
|
19
|
+
EXPOSE 8080
|
|
20
|
+
|
|
21
|
+
# Run the web service on container startup.
|
|
22
|
+
CMD [ "npm", "start" ]
|
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import polka from 'polka';
|
|
2
|
+
import sirv from 'sirv';
|
|
3
|
+
import compression from 'compression';
|
|
4
|
+
import bodyParser from 'body-parser';
|
|
5
|
+
import colors from 'kleur';
|
|
6
|
+
import { config as dotenv } from 'dotenv';
|
|
7
|
+
import { Configuration, Scout9Api } from '@scout9/admin';
|
|
8
|
+
import { EventResponse } from '@scout9/app';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import https from 'node:https';
|
|
12
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
13
|
+
import projectApp from './src/app.js';
|
|
14
|
+
import config from './config.js';
|
|
15
|
+
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = path.dirname(__filename);
|
|
18
|
+
|
|
19
|
+
const dev = process.env.DEV_MODE === 'true' || process.env.TEST_MODE === 'true';
|
|
20
|
+
|
|
21
|
+
class ServerCache {
|
|
22
|
+
constructor(filePath = path.resolve(__dirname, './server.cache.json')) {
|
|
23
|
+
this.filePath = filePath;
|
|
24
|
+
this._load();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
isTested() {
|
|
28
|
+
return this._cache.tested === true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
setTested(value = true) {
|
|
32
|
+
this._cache.tested = value;
|
|
33
|
+
this._save();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
reset(override = {tested: false}) {
|
|
37
|
+
this._save(override);
|
|
38
|
+
this._cache = override;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
_load() {
|
|
42
|
+
try {
|
|
43
|
+
this._cache = JSON.parse(fs.readFileSync(this.filePath, 'utf8'));
|
|
44
|
+
return this._cache;
|
|
45
|
+
} catch (e) {
|
|
46
|
+
if (e.code === 'ENOENT') {
|
|
47
|
+
this._save();
|
|
48
|
+
} else {
|
|
49
|
+
throw e;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
_save(override) {
|
|
55
|
+
fs.writeFileSync(this.filePath, JSON.stringify(override || this._cache || {tested: false}));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
// Ensure .env config is set (specifically SCOUT9_API_KEY)
|
|
61
|
+
const configFilePath = path.resolve(process.cwd(), './.env');
|
|
62
|
+
dotenv({path: configFilePath});
|
|
63
|
+
|
|
64
|
+
const configuration = new Configuration({
|
|
65
|
+
apiKey: process.env.SCOUT9_API_KEY || ''
|
|
66
|
+
});
|
|
67
|
+
const scout9 = new Scout9Api(configuration);
|
|
68
|
+
const cache = new ServerCache();
|
|
69
|
+
cache.reset();
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
const handleError = (e, res = undefined) => {
|
|
73
|
+
let name = e?.name || 'Runtime Error';
|
|
74
|
+
let message = e?.message || 'Unknown error';
|
|
75
|
+
let code = typeof e?.code === 'number'
|
|
76
|
+
? e?.code
|
|
77
|
+
: typeof e?.status === 'number'
|
|
78
|
+
? e?.status
|
|
79
|
+
: 500;
|
|
80
|
+
if ('response' in e) {
|
|
81
|
+
const response = e.response;
|
|
82
|
+
if (response?.status) {
|
|
83
|
+
code = response.status;
|
|
84
|
+
}
|
|
85
|
+
if (response?.statusText) {
|
|
86
|
+
name = response.statusText;
|
|
87
|
+
}
|
|
88
|
+
if (response?.data) {
|
|
89
|
+
message = response.data;
|
|
90
|
+
} else if (response?.body) {
|
|
91
|
+
message = response.body;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
console.log(colors.red(`${colors.bold(`${code} Error`)}: ${message}`));
|
|
95
|
+
if (res) {
|
|
96
|
+
res.writeHead(code, {'Content-Type': 'application/json'});
|
|
97
|
+
res.end(JSON.stringify({
|
|
98
|
+
name,
|
|
99
|
+
error: message,
|
|
100
|
+
code
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const makeRequest = async (options, maxRedirects = 10) => {
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
107
|
+
|
|
108
|
+
if (maxRedirects < 0) {
|
|
109
|
+
reject(new Error('Too many redirects'));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const req = https.request(options, (res) => {
|
|
114
|
+
console.log(`STATUS: ${res.statusCode}`);
|
|
115
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
116
|
+
// Handle redirect
|
|
117
|
+
console.log(`Redirecting to ${res.headers.location}`);
|
|
118
|
+
const newUrl = new URL(res.headers.location);
|
|
119
|
+
const newOptions = {
|
|
120
|
+
hostname: newUrl.hostname,
|
|
121
|
+
port: newUrl.port || 443,
|
|
122
|
+
path: newUrl.pathname,
|
|
123
|
+
method: 'GET', // Usually redirects are GET, adjust if necessary
|
|
124
|
+
headers: options.headers // Reuse original headers
|
|
125
|
+
// Add any other necessary options here
|
|
126
|
+
};
|
|
127
|
+
// Recursive call to handle redirect
|
|
128
|
+
resolve(makeRequest(newOptions, maxRedirects - 1));
|
|
129
|
+
} else {
|
|
130
|
+
let data = '';
|
|
131
|
+
res.setEncoding('utf8');
|
|
132
|
+
res.on('data', (chunk) => data += chunk);
|
|
133
|
+
res.on('end', () => {
|
|
134
|
+
try {
|
|
135
|
+
resolve(JSON.parse(data));
|
|
136
|
+
} catch (e) {
|
|
137
|
+
reject(e);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
req.on('error', (e) => {
|
|
144
|
+
reject(e);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
req.end();
|
|
148
|
+
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
const app = polka();
|
|
154
|
+
|
|
155
|
+
app.use(bodyParser.json());
|
|
156
|
+
|
|
157
|
+
if (dev) {
|
|
158
|
+
app.use(compression());
|
|
159
|
+
app.use(sirv(path.resolve(__dirname, 'public'), {dev: true}));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Root application POST endpoint will run the scout9 app
|
|
163
|
+
app.post(dev ? '/dev/workflow' : '/', async (req, res) => {
|
|
164
|
+
try {
|
|
165
|
+
// @TODO use zod to check if req.body is a valid event object
|
|
166
|
+
const response = await projectApp(req.body);
|
|
167
|
+
res.writeHead(200, {'Content-Type': 'application/json'});
|
|
168
|
+
res.end(JSON.stringify(response));
|
|
169
|
+
} catch (e) {
|
|
170
|
+
handleError(e, res);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
function isSurroundedByBrackets(str) {
|
|
175
|
+
return /^\[.*\]$/.test(str);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function resolveEntity(entity, method) {
|
|
179
|
+
const entityField = config.entities.find(e => e.entity === entity);
|
|
180
|
+
if (!entityField) {
|
|
181
|
+
console.error(`Invalid entity: "${entity}" not found within [${config.entities.map(e => e.entity).join(', ')}]`);
|
|
182
|
+
throw new Error(`Invalid entity: not found`);
|
|
183
|
+
}
|
|
184
|
+
const {api, entities} = entityField;
|
|
185
|
+
if (!api && !api[method]) {
|
|
186
|
+
throw new Error(`Invalid entity: no API`);
|
|
187
|
+
}
|
|
188
|
+
if (!entities) {
|
|
189
|
+
throw new Error(`Invalid entity: no path`);
|
|
190
|
+
}
|
|
191
|
+
return entityField;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function resolveEntityApi(entity, method) {
|
|
195
|
+
const paramEntity = isSurroundedByBrackets(entity);
|
|
196
|
+
if (method === 'GET' && !paramEntity) {
|
|
197
|
+
method = 'QUERY';
|
|
198
|
+
}
|
|
199
|
+
const methods = ['GET', 'UPDATE', 'QUERY', 'PUT', 'PATCH', 'DELETE'];
|
|
200
|
+
if (!methods.includes(method)) {
|
|
201
|
+
throw new Error(`Invalid method: ${method}`);
|
|
202
|
+
}
|
|
203
|
+
const {api, entities} = resolveEntity(entity, method);
|
|
204
|
+
const mod = await import(pathToFileURL(path.resolve(__dirname, `./src/entities/${entities.join('/')}/api.js`)).href)
|
|
205
|
+
.catch((e) => {
|
|
206
|
+
switch (e.code) {
|
|
207
|
+
case 'ERR_MODULE_NOT_FOUND':
|
|
208
|
+
case 'MODULE_NOT_FOUND':
|
|
209
|
+
console.error(e);
|
|
210
|
+
throw new Error(`Invalid entity: no API method`);
|
|
211
|
+
default:
|
|
212
|
+
console.error(e);
|
|
213
|
+
throw new Error(`Invalid entity: Internal system error`);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
if (mod[method]) {
|
|
217
|
+
return mod[method];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (method === 'QUERY' && mod['GET']) {
|
|
221
|
+
return mod['GET'];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
throw new Error(`Invalid entity: no API method`);
|
|
225
|
+
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function extractParamsFromPath(path) {
|
|
229
|
+
const segments = path.split('/').filter(Boolean); // Split and remove empty segments
|
|
230
|
+
let params = {};
|
|
231
|
+
const dataStructure = config.entities;
|
|
232
|
+
|
|
233
|
+
// Assuming the structure starts with "/entity/"
|
|
234
|
+
segments.shift(); // remove the "entity" segment
|
|
235
|
+
|
|
236
|
+
let lastEntity;
|
|
237
|
+
let lastSegment;
|
|
238
|
+
segments.forEach(segment => {
|
|
239
|
+
const isEntity = dataStructure.some(d => d.entity === segment || d.entity === `[${segment}]`);
|
|
240
|
+
if (isEntity) {
|
|
241
|
+
lastEntity = segment;
|
|
242
|
+
lastSegment = segment;
|
|
243
|
+
} else if (lastEntity) {
|
|
244
|
+
const entityDefinition = dataStructure.find(d => {
|
|
245
|
+
const index = d.entities.indexOf(lastEntity);
|
|
246
|
+
return index > -1 && index === (d.entities.length - 2);
|
|
247
|
+
});
|
|
248
|
+
if (entityDefinition) {
|
|
249
|
+
const paramName = entityDefinition.entity.replace(/[\[\]]/g, ''); // Remove brackets to get the param name
|
|
250
|
+
params[paramName] = segment;
|
|
251
|
+
lastSegment = entityDefinition.entity;
|
|
252
|
+
}
|
|
253
|
+
lastEntity = null; // Reset for next potential entity-instance pair
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
return {params, lastEntity, lastSegment};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function runEntityApi(req, res) {
|
|
261
|
+
try {
|
|
262
|
+
// polka doesn't support wildcards
|
|
263
|
+
const {params, lastSegment} = extractParamsFromPath(req.url);
|
|
264
|
+
const api = await resolveEntityApi(lastSegment, req.method.toUpperCase());
|
|
265
|
+
const response = await api({
|
|
266
|
+
params,
|
|
267
|
+
searchParams: req?.query || {}, body: req?.body || undefined,
|
|
268
|
+
id: params.id
|
|
269
|
+
});
|
|
270
|
+
if (response instanceof EventResponse && !!response.body) {
|
|
271
|
+
res.writeHead(response.status || 200, {'Content-Type': 'application/json'});
|
|
272
|
+
res.end(JSON.stringify(response.body));
|
|
273
|
+
} else {
|
|
274
|
+
throw new Error(`Invalid response: not an EventResponse`);
|
|
275
|
+
}
|
|
276
|
+
} catch (e) {
|
|
277
|
+
console.error(e);
|
|
278
|
+
res.writeHead(500, {'Content-Type': 'application/json'});
|
|
279
|
+
res.end(JSON.stringify({error: e.message}));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
app.get('/entity/:entity', runEntityApi);
|
|
284
|
+
app.put('/entity/:entity', runEntityApi);
|
|
285
|
+
app.patch('/entity/:entity', runEntityApi);
|
|
286
|
+
app.post('/entity/:entity', runEntityApi);
|
|
287
|
+
app.delete('/entity/:entity', runEntityApi);
|
|
288
|
+
app.get('/entity/:entity/*', runEntityApi);
|
|
289
|
+
app.put('/entity/:entity/*', runEntityApi);
|
|
290
|
+
app.patch('/entity/:entity/*', runEntityApi);
|
|
291
|
+
app.post('/entity/:entity/*', runEntityApi);
|
|
292
|
+
app.delete('/entity/:entity/*', runEntityApi);
|
|
293
|
+
|
|
294
|
+
// For local development: parse a message
|
|
295
|
+
if (dev) {
|
|
296
|
+
|
|
297
|
+
app.get('/dev/config', async (req, res, next) => {
|
|
298
|
+
|
|
299
|
+
// Retrieve auth token
|
|
300
|
+
const {token, id} = await makeRequest({
|
|
301
|
+
hostname: 'us-central1-jumpstart.cloudfunctions.net',
|
|
302
|
+
port: 443,
|
|
303
|
+
path: '/v1-utils-platform-token',
|
|
304
|
+
method: 'GET',
|
|
305
|
+
headers: {
|
|
306
|
+
'Authorization': 'Bearer ' + process.env.SCOUT9_API_KEY
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
res.writeHead(200, {'Content-Type': 'application/json'});
|
|
310
|
+
res.end(JSON.stringify({token, id, ...config}));
|
|
311
|
+
try {
|
|
312
|
+
if (!cache.isTested()) {
|
|
313
|
+
const testableEntities = config.entities.filter(e => e?.definitions?.length > 0 || e?.training?.length > 0);
|
|
314
|
+
if (dev && testableEntities.length > 0) {
|
|
315
|
+
console.log(`${colors.grey(`${colors.cyan('>')} Testing ${colors.bold(colors.white(testableEntities.length))} Entities...`)}`);
|
|
316
|
+
const _res = await scout9.parse({
|
|
317
|
+
message: 'Dummy message to parse',
|
|
318
|
+
language: 'en',
|
|
319
|
+
entities: testableEntities
|
|
320
|
+
});
|
|
321
|
+
cache.setTested();
|
|
322
|
+
console.log(`\t${colors.green(`+ ${testableEntities.length} Entities passed`)}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
} catch (e) {
|
|
326
|
+
console.error(e);
|
|
327
|
+
handleError(e);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
app.post('/dev/parse', async (req, res, next) => {
|
|
332
|
+
try {
|
|
333
|
+
// req.body: {message: string}
|
|
334
|
+
const {message, language} = req.body;
|
|
335
|
+
if (typeof message !== 'string') {
|
|
336
|
+
throw new Error('Invalid message - expected to be a string');
|
|
337
|
+
}
|
|
338
|
+
console.log(`${colors.grey(`${colors.cyan('>')} Parsing "${colors.bold(colors.white(message))}`)}"`);
|
|
339
|
+
const payload = await scout9.parse({
|
|
340
|
+
message,
|
|
341
|
+
language: 'en',
|
|
342
|
+
entities: config.entities
|
|
343
|
+
}).then((_res => _res.data));
|
|
344
|
+
let fields = '';
|
|
345
|
+
for (const [key, value] of Object.entries(payload.context)) {
|
|
346
|
+
fields += `\n\t\t${colors.bold(colors.white(key))}: ${colors.grey(JSON.stringify(value))}`;
|
|
347
|
+
}
|
|
348
|
+
console.log(`\tParsed in ${payload.ms}ms:${colors.grey(`${fields}`)}`);
|
|
349
|
+
res.writeHead(200, {'Content-Type': 'application/json'});
|
|
350
|
+
res.end(JSON.stringify(payload));
|
|
351
|
+
} catch (e) {
|
|
352
|
+
handleError(e, res);
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
app.post('/dev/forward', async (req, res, next) => {
|
|
357
|
+
try {
|
|
358
|
+
// req.body: {message: string}
|
|
359
|
+
const {convo} = req.body;
|
|
360
|
+
console.log(`${colors.grey(`${colors.cyan('>')} Forwarding...`)}`);
|
|
361
|
+
const payload = await scout9.forward({convo}).then((_res => _res.data));
|
|
362
|
+
console.log(`\tForwarded in ${payload?.ms}ms`);
|
|
363
|
+
res.writeHead(200, {'Content-Type': 'application/json'});
|
|
364
|
+
res.end(JSON.stringify(payload));
|
|
365
|
+
} catch (e) {
|
|
366
|
+
handleError(e, res);
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
app.post('/dev/generate', async (req, res, next) => {
|
|
371
|
+
try {
|
|
372
|
+
// req.body: {conversation: {}, messages: []}
|
|
373
|
+
const {messages, persona: personaId} = req.body;
|
|
374
|
+
if (typeof messages !== 'object' || !Array.isArray(messages)) {
|
|
375
|
+
throw new Error('Invalid messages array - expected to be an array of objects');
|
|
376
|
+
}
|
|
377
|
+
if (typeof personaId !== 'string') {
|
|
378
|
+
throw new Error('Invalid persona - expected to be a string');
|
|
379
|
+
}
|
|
380
|
+
const persona = (config.persona || config.agents).find(p => p.id === personaId);
|
|
381
|
+
if (!persona) {
|
|
382
|
+
throw new Error(`Could not find persona with id: ${personaId}, ensure your project is sync'd by running "scout9 sync"`);
|
|
383
|
+
}
|
|
384
|
+
console.log(`${colors.grey(`${colors.cyan('>')} Generating ${colors.bold(colors.white(persona.firstName))}'s`)} ${colors.bold(
|
|
385
|
+
colors.red(colors.bgBlack('auto-reply')))}`);
|
|
386
|
+
const payload = await scout9.generate({
|
|
387
|
+
messages,
|
|
388
|
+
persona,
|
|
389
|
+
llm: config.llm,
|
|
390
|
+
pmt: config.pmt
|
|
391
|
+
}).then((_res => _res.data));
|
|
392
|
+
console.log(`\t${colors.grey(`Response: ${colors.green('"')}${colors.bold(colors.white(payload.message))}`)}${colors.green(
|
|
393
|
+
'"')} (elapsed ${payload.ms}ms)`);
|
|
394
|
+
res.writeHead(200, {'Content-Type': 'application/json'});
|
|
395
|
+
res.end(JSON.stringify(payload));
|
|
396
|
+
} catch (e) {
|
|
397
|
+
handleError(e, res);
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
app.listen(process.env.PORT || 8080, err => {
|
|
404
|
+
if (err) throw err;
|
|
405
|
+
|
|
406
|
+
const art_scout9 = `
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
________ ________ ________ ___ ___ _________ ________
|
|
410
|
+
|\\ ____\\|\\ ____\\|\\ __ \\|\\ \\|\\ \\|\\___ ___\\\\ ___ \\
|
|
411
|
+
\\ \\ \\___|\\ \\ \\___|\\ \\ \\|\\ \\ \\ \\\\\\ \\|___ \\ \\_\\ \\____ \\
|
|
412
|
+
\\ \\_____ \\ \\ \\ \\ \\ \\\\\\ \\ \\ \\\\\\ \\ \\ \\ \\ \\|____|\\ \\
|
|
413
|
+
\\|____|\\ \\ \\ \\____\\ \\ \\\\\\ \\ \\ \\\\\\ \\ \\ \\ \\ __\\_\\ \\
|
|
414
|
+
____\\_\\ \\ \\_______\\ \\_______\\ \\_______\\ \\ \\__\\ |\\_______\\
|
|
415
|
+
|\\_________\\|_______|\\|_______|\\|_______| \\|__| \\|_______|
|
|
416
|
+
\\|_________|
|
|
417
|
+
`;
|
|
418
|
+
const art_auto_reply = ` ___ __ ____ __ __
|
|
419
|
+
/ | __ __/ /_____ / __ \\___ ____ / /_ __ / /
|
|
420
|
+
/ /| |/ / / / __/ __ \\ / /_/ / _ \\/ __ \\/ / / / / / /
|
|
421
|
+
/ ___ / /_/ / /_/ /_/ / / _, _/ __/ /_/ / / /_/ / /_/
|
|
422
|
+
/_/ |_\\__,_/\\__/\\____/ /_/ |_|\\___/ .___/_/\\__, / (_)
|
|
423
|
+
/_/ /____/
|
|
424
|
+
|
|
425
|
+
`;
|
|
426
|
+
const protocol = process.env.PROTOCOL || 'http';
|
|
427
|
+
const host = process.env.HOST || 'localhost';
|
|
428
|
+
const port = process.env.PORT || 8080;
|
|
429
|
+
const fullUrl = `${protocol}://${host}:${port}`;
|
|
430
|
+
if (dev) {
|
|
431
|
+
console.log(colors.bold(colors.green(art_scout9)));
|
|
432
|
+
console.log(colors.bold(colors.cyan(art_auto_reply)));
|
|
433
|
+
console.log(`${colors.grey(`${colors.cyan('>')} Running ${colors.bold(colors.white('Scout9'))}`)} ${colors.bold(
|
|
434
|
+
colors.red(colors.bgBlack('auto-reply')))} ${colors.grey('dev environment on')} ${fullUrl}`);
|
|
435
|
+
} else {
|
|
436
|
+
console.log(`Running Scout9 auto-reply app on ${fullUrl}`);
|
|
437
|
+
}
|
|
438
|
+
// Run checks
|
|
439
|
+
if (!fs.existsSync(configFilePath)) {
|
|
440
|
+
console.log(colors.red('Missing .env file, your auto reply application may not work without it.'));
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (dev && !process.env.SCOUT9_API_KEY) {
|
|
444
|
+
console.log(colors.red(
|
|
445
|
+
'Missing SCOUT9_API_KEY environment variable, your auto reply application may not work without it.'));
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (process.env.SCOUT9_API_KEY === '<insert-scout9-api-key>') {
|
|
449
|
+
console.log(`${colors.red('SCOUT9_API_KEY has not been set in your .env file.')} ${colors.grey(
|
|
450
|
+
'You can find your API key in the Scout9 dashboard.')} ${colors.bold(colors.cyan('https://scout9.com'))}`);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import { globSync } from 'glob';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
const resolveFilePath = () => {
|
|
6
|
+
const paths = globSync(`${src}/entities/agents/{index,config}.{ts,js}`, {cwd, absolute: true});
|
|
7
|
+
if (paths.length === 0) {
|
|
8
|
+
throw new Error(`Missing required agents entity file, rerun "scout9 sync" to fix`);
|
|
9
|
+
}
|
|
10
|
+
if (paths.length > 1) {
|
|
11
|
+
throw new Error(`Multiple agents entity files found, rerun "scout9 sync" to fix`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const entities = {
|
|
16
|
+
/**
|
|
17
|
+
* Generates ./src/entities/
|
|
18
|
+
* @param {import('../../runtime/client/config.js').IScout9ProjectBuildConfig} config
|
|
19
|
+
* @param {string} cwd
|
|
20
|
+
* @param {string} src
|
|
21
|
+
* @returns {Promise<void>}
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
agents: async function (config, cwd = process.cwd(), src = './src') {
|
|
25
|
+
const content = `
|
|
26
|
+
/**
|
|
27
|
+
* Required core entity type: Agents represents you and your team
|
|
28
|
+
* @returns {Array<Agent>}
|
|
29
|
+
*/
|
|
30
|
+
export default function Agents() {
|
|
31
|
+
return ${JSON.stringify(config.agents, null, 2)};
|
|
32
|
+
}
|
|
33
|
+
`;
|
|
34
|
+
await fs.writeFile(filePath, );
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "templates",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Used to ensure template app.js gets the correct dependencies.",
|
|
5
|
+
"dependencies": {
|
|
6
|
+
"body-parser": "latest",
|
|
7
|
+
"compression": "latest",
|
|
8
|
+
"dotenv": "latest",
|
|
9
|
+
"kleur": "latest",
|
|
10
|
+
"polka": "latest",
|
|
11
|
+
"sirv": "latest"
|
|
12
|
+
}
|
|
13
|
+
}
|
package/src/exports.js
CHANGED
|
@@ -2,35 +2,39 @@ import { Scout9Platform } from './platform.js';
|
|
|
2
2
|
import { EventResponse } from './runtime/index.js';
|
|
3
3
|
|
|
4
4
|
export { EventResponse } from './runtime/index.js';
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
export * from './testing-tools/index.js';
|
|
7
|
+
export * from './runtime/client/index.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
|
-
* @param event
|
|
10
|
-
* @param
|
|
11
|
-
* @param
|
|
12
|
-
* @
|
|
10
|
+
* @param {import('./runtime/client/workflow.js').IWorkflowEvent} event - every workflow receives an event object
|
|
11
|
+
* @param {Object} options
|
|
12
|
+
* @param {string} [options.cwd=process.cwd()] - the working directory
|
|
13
|
+
* @param {string} [options.mode='production'] - the build mode
|
|
14
|
+
* @param {string} [options.src='./src'] - the source directory
|
|
15
|
+
* @param {string} options.eventSource - the source of the workflow event
|
|
16
|
+
* @returns {Promise<import('./runtime/client/workflow.js').IWorkflowResponse>}
|
|
13
17
|
*/
|
|
14
|
-
export async function run(
|
|
15
|
-
event,
|
|
16
|
-
{cwd = process.cwd(), folder} = {},
|
|
17
|
-
) {
|
|
18
|
-
return Scout9Platform.run(event, {cwd, folder})
|
|
18
|
+
export async function run(event, options) {
|
|
19
|
+
return Scout9Platform.run(event, options)
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
/**
|
|
23
|
+
* @param {import('./runtime/client/workflow.js').IWorkflowEvent} event - every workflow receives an event object
|
|
24
|
+
* @param {{cwd: string; mode: 'development' | 'production'; src: string}} options - build options
|
|
25
|
+
* @returns {Promise<import('./runtime/client/workflow.js').IWorkflowResponse>}
|
|
26
|
+
*/
|
|
27
|
+
export const sendEvent = run;
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* @param data {T}
|
|
30
31
|
* @param init {ResponseInit | undefined}
|
|
31
|
-
* @returns {EventResponse<T>}
|
|
32
|
+
* @returns {import('./runtime/client/api.js').EventResponse<T>}
|
|
32
33
|
*/
|
|
33
34
|
export function json(data, init) {
|
|
35
|
+
if (data instanceof Promise) {
|
|
36
|
+
throw new Error(`json() does not expect a Promise. Use json(await promise) instead`);
|
|
37
|
+
}
|
|
34
38
|
// TODO deprecate this in favour of `Response.json` when it's
|
|
35
39
|
// more widely supported
|
|
36
40
|
const body = JSON.stringify(data);
|