@recombine-ai/engine 0.1.1
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/build/index.d.ts +5 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +23 -0
- package/build/lib/ai.d.ts +88 -0
- package/build/lib/ai.d.ts.map +1 -0
- package/build/lib/ai.js +234 -0
- package/build/lib/bosun/action.d.ts +10 -0
- package/build/lib/bosun/action.d.ts.map +1 -0
- package/build/lib/bosun/action.js +37 -0
- package/build/lib/bosun/agent.d.ts +25 -0
- package/build/lib/bosun/agent.d.ts.map +1 -0
- package/build/lib/bosun/agent.js +6 -0
- package/build/lib/bosun/context.d.ts +22 -0
- package/build/lib/bosun/context.d.ts.map +1 -0
- package/build/lib/bosun/context.js +42 -0
- package/build/lib/bosun/index.d.ts +5 -0
- package/build/lib/bosun/index.d.ts.map +1 -0
- package/build/lib/bosun/index.js +20 -0
- package/build/lib/bosun/mock.d.ts +6 -0
- package/build/lib/bosun/mock.d.ts.map +1 -0
- package/build/lib/bosun/mock.js +32 -0
- package/build/lib/bosun/test-workflow.d.ts +24 -0
- package/build/lib/bosun/test-workflow.d.ts.map +1 -0
- package/build/lib/bosun/test-workflow.js +11 -0
- package/build/lib/bosun/workflow.d.ts +25 -0
- package/build/lib/bosun/workflow.d.ts.map +1 -0
- package/build/lib/bosun/workflow.js +6 -0
- package/build/lib/interfaces.d.ts +32 -0
- package/build/lib/interfaces.d.ts.map +1 -0
- package/build/lib/interfaces.js +2 -0
- package/build/lib/schedule.d.ts +25 -0
- package/build/lib/schedule.d.ts.map +1 -0
- package/build/lib/schedule.js +100 -0
- package/build/lib/test-workflow.d.ts +24 -0
- package/build/lib/test-workflow.d.ts.map +1 -0
- package/build/lib/test-workflow.js +11 -0
- package/package.json +29 -0
- package/readme.md +103 -0
package/build/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAExD,OAAO,EAAE,YAAY,EAAE,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAE5D,cAAc,kBAAkB,CAAA;AAEhC,cAAc,aAAa,CAAA"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.delayFactory = exports.createAIEngine = void 0;
|
|
18
|
+
var ai_1 = require("./lib/ai");
|
|
19
|
+
Object.defineProperty(exports, "createAIEngine", { enumerable: true, get: function () { return ai_1.createAIEngine; } });
|
|
20
|
+
var schedule_1 = require("./lib/schedule");
|
|
21
|
+
Object.defineProperty(exports, "delayFactory", { enumerable: true, get: function () { return schedule_1.delayFactory; } });
|
|
22
|
+
__exportStar(require("./lib/interfaces"), exports);
|
|
23
|
+
__exportStar(require("./lib/bosun"), exports);
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { ZodSchema } from 'zod';
|
|
2
|
+
import { Logger, Message } from './interfaces';
|
|
3
|
+
import { SendAction } from './bosun/action';
|
|
4
|
+
export interface Step {
|
|
5
|
+
/** Step name (used mainly for debugging) */
|
|
6
|
+
name: string;
|
|
7
|
+
/**
|
|
8
|
+
* Prompt can be a simple string or a link to a file, loaded with `loadFile` function which
|
|
9
|
+
* takes a path to the file relative to `src/use-cases` directory.
|
|
10
|
+
*/
|
|
11
|
+
prompt: string | File;
|
|
12
|
+
/**
|
|
13
|
+
* In case you want a structured from LLM, define a schema using {@link zod https://zod.dev/}
|
|
14
|
+
* library.
|
|
15
|
+
*/
|
|
16
|
+
schema?: ZodSchema;
|
|
17
|
+
/** Exclude directives from message history for this step */
|
|
18
|
+
ignoreDirectives?: boolean;
|
|
19
|
+
/** Additional data to be inserted into prompt */
|
|
20
|
+
context?: Record<string, unknown>;
|
|
21
|
+
/** Check a condition, whether the `execute` function should run or not */
|
|
22
|
+
shouldExecute?: (reply: string) => boolean | Promise<boolean>;
|
|
23
|
+
/** Check a condition, whether the whole step should be run or not */
|
|
24
|
+
runIf?: (messages: Messages) => boolean | Promise<boolean>;
|
|
25
|
+
/** Use when you need to do some action when LLM's response received */
|
|
26
|
+
execute: (reply: string) => Promise<unknown>;
|
|
27
|
+
/** Error handler called if an error occurred during LLM API call or in `execute` function */
|
|
28
|
+
onError: (error: string) => Promise<unknown>;
|
|
29
|
+
/**
|
|
30
|
+
* When provided, throws an error if the step is invoked more times than `maxAttempts`.
|
|
31
|
+
* Number of attempts taken is reset when `shouldExecute` returns `false`. Useful to limit
|
|
32
|
+
* rewinds by reviewers. NOTE that it doesn't work on steps without `shouldExecute` method.
|
|
33
|
+
*/
|
|
34
|
+
maxAttempts?: number;
|
|
35
|
+
}
|
|
36
|
+
export interface DumbStep {
|
|
37
|
+
name: string;
|
|
38
|
+
runIf?: () => boolean | Promise<boolean>;
|
|
39
|
+
execute: () => Promise<unknown>;
|
|
40
|
+
onError: (error: string) => Promise<unknown>;
|
|
41
|
+
}
|
|
42
|
+
export type CreateStep = ReturnType<typeof createAIEngine>['createStep'];
|
|
43
|
+
export type CreateWorkflow = ReturnType<typeof createAIEngine>['createWorkflow'];
|
|
44
|
+
export type LoadFile = ReturnType<typeof createAIEngine>['loadFile'];
|
|
45
|
+
export type MakeMessagesList = ReturnType<typeof createAIEngine>['makeMessagesList'];
|
|
46
|
+
export type AiEngine = {
|
|
47
|
+
createWorkflow: CreateWorkflow;
|
|
48
|
+
createStep: CreateStep;
|
|
49
|
+
loadFile: LoadFile;
|
|
50
|
+
makeMessagesList: MakeMessagesList;
|
|
51
|
+
};
|
|
52
|
+
export interface Messages {
|
|
53
|
+
setUserName(name: string): void;
|
|
54
|
+
setAgentName(name: string): void;
|
|
55
|
+
toString: (ignoreDirectives?: boolean) => string;
|
|
56
|
+
addDirective: (message: string) => void;
|
|
57
|
+
addMessage: (name: Message['sender'], message: string) => void;
|
|
58
|
+
directiveFormat: (formatter: (message: Message) => string) => void;
|
|
59
|
+
proposedMessageFormat: (formatter: (message: string) => string) => void;
|
|
60
|
+
setProposedReply: (message: string) => void;
|
|
61
|
+
getProposedReply: () => string | null;
|
|
62
|
+
getHistory: () => Message[];
|
|
63
|
+
}
|
|
64
|
+
export interface File {
|
|
65
|
+
content: () => Promise<string>;
|
|
66
|
+
}
|
|
67
|
+
export interface EngineConfig {
|
|
68
|
+
tokenStorage?: {
|
|
69
|
+
getToken: () => Promise<string | null>;
|
|
70
|
+
};
|
|
71
|
+
basePath?: string;
|
|
72
|
+
logger?: Logger;
|
|
73
|
+
sendAction?: SendAction;
|
|
74
|
+
}
|
|
75
|
+
export declare function createAIEngine(cfg?: EngineConfig): {
|
|
76
|
+
createWorkflow: (...steps: Array<Step | DumbStep>) => Promise<{
|
|
77
|
+
terminate: () => void;
|
|
78
|
+
run: (messages: Messages) => Promise<string | null>;
|
|
79
|
+
rewindTo: (step: Step) => void;
|
|
80
|
+
beforeEach(callback: () => Promise<unknown>): void;
|
|
81
|
+
}>;
|
|
82
|
+
createStep: <T extends Step | DumbStep>(step: T) => T;
|
|
83
|
+
loadFile: (path: string) => {
|
|
84
|
+
content: () => Promise<string>;
|
|
85
|
+
};
|
|
86
|
+
makeMessagesList: (messages?: Message[]) => Messages;
|
|
87
|
+
};
|
|
88
|
+
//# sourceMappingURL=ai.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai.d.ts","sourceRoot":"","sources":["../../src/lib/ai.ts"],"names":[],"mappings":"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"}
|
package/build/lib/ai.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createAIEngine = createAIEngine;
|
|
7
|
+
// cspell:words lstripBlocks
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const openai_1 = __importDefault(require("openai"));
|
|
10
|
+
const path_1 = require("path");
|
|
11
|
+
const nunjucks_1 = __importDefault(require("nunjucks"));
|
|
12
|
+
const zod_to_json_schema_1 = require("zod-to-json-schema");
|
|
13
|
+
const action_1 = require("./bosun/action");
|
|
14
|
+
const core_1 = require("openai/core");
|
|
15
|
+
function createAIEngine(cfg = {}) {
|
|
16
|
+
const logger = cfg.logger || globalThis.console;
|
|
17
|
+
const basePath = cfg.basePath || process.cwd();
|
|
18
|
+
const tokenStorage = cfg.tokenStorage || {
|
|
19
|
+
async getToken() {
|
|
20
|
+
if (process.env.OPENAI_API_KEY) {
|
|
21
|
+
return process.env.OPENAI_API_KEY;
|
|
22
|
+
}
|
|
23
|
+
throw new Error('OpenAI API key is not set');
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
function createStep(step) {
|
|
27
|
+
return step;
|
|
28
|
+
}
|
|
29
|
+
function makeMessagesList(messages = []) {
|
|
30
|
+
let directivesFormatter = (message) => `${message.sender}: ${message.text}`;
|
|
31
|
+
let proposedFormatter = (message) => `Proposed reply: ${message}`;
|
|
32
|
+
let proposedReply = null;
|
|
33
|
+
const names = {
|
|
34
|
+
agent: 'Agent',
|
|
35
|
+
user: 'User',
|
|
36
|
+
system: 'System',
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
toString: (ignoreDirectives = false) => messages
|
|
40
|
+
.map((msg) => {
|
|
41
|
+
if (msg.sender === 'system') {
|
|
42
|
+
return ignoreDirectives ? null : directivesFormatter(msg);
|
|
43
|
+
}
|
|
44
|
+
return `${names[msg.sender]}: ${msg.text}`;
|
|
45
|
+
})
|
|
46
|
+
.filter((msg) => msg !== null)
|
|
47
|
+
.join('\n') + (proposedReply ? `\n${proposedFormatter(proposedReply)}` : ''),
|
|
48
|
+
addMessage: (sender, text) => messages.push({ sender, text }),
|
|
49
|
+
addDirective: (message) => {
|
|
50
|
+
logger.debug(`AI Core: add directive: ${message}`);
|
|
51
|
+
messages.push({ sender: 'system', text: message });
|
|
52
|
+
},
|
|
53
|
+
directiveFormat: (formatter) => {
|
|
54
|
+
directivesFormatter = formatter;
|
|
55
|
+
},
|
|
56
|
+
proposedMessageFormat: (formatter) => {
|
|
57
|
+
proposedFormatter = formatter;
|
|
58
|
+
},
|
|
59
|
+
setProposedReply: (message) => (proposedReply = message),
|
|
60
|
+
getProposedReply: () => proposedReply,
|
|
61
|
+
getHistory: () => messages,
|
|
62
|
+
setUserName: (name) => {
|
|
63
|
+
names.user = name;
|
|
64
|
+
},
|
|
65
|
+
setAgentName: (name) => {
|
|
66
|
+
names.agent = name;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
async function createWorkflow(...steps) {
|
|
71
|
+
const apiKey = await tokenStorage.getToken();
|
|
72
|
+
let shouldRun = true;
|
|
73
|
+
let currentStep = 0;
|
|
74
|
+
let beforeEachCallback = async () => Promise.resolve(null);
|
|
75
|
+
const attempts = new Map();
|
|
76
|
+
return {
|
|
77
|
+
terminate: () => {
|
|
78
|
+
logger.debug('AI Core: Terminating conversation...');
|
|
79
|
+
shouldRun = false;
|
|
80
|
+
},
|
|
81
|
+
run: async (messages) => {
|
|
82
|
+
for (; currentStep < steps.length; currentStep++) {
|
|
83
|
+
await beforeEachCallback();
|
|
84
|
+
const step = steps[currentStep];
|
|
85
|
+
if (!shouldRun) {
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
if (!step.runIf || (await step.runIf(messages))) {
|
|
89
|
+
const action = (0, action_1.makeAction)(cfg.sendAction, 'AI', step.name);
|
|
90
|
+
await action('started');
|
|
91
|
+
logger.debug(`AI Core: Step: ${step.name}`);
|
|
92
|
+
if ('prompt' in step) {
|
|
93
|
+
await runStep(step, messages);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
await runDumbStep(step);
|
|
97
|
+
}
|
|
98
|
+
await action('completed');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return shouldRun ? messages.getProposedReply() : null;
|
|
102
|
+
},
|
|
103
|
+
rewindTo: (step) => {
|
|
104
|
+
const index = steps.indexOf(step);
|
|
105
|
+
if (index === -1) {
|
|
106
|
+
throw new Error(`Step ${step.name} not found`);
|
|
107
|
+
}
|
|
108
|
+
if (index > currentStep) {
|
|
109
|
+
throw new Error(`Cannot rewind to a step ahead of the current step`);
|
|
110
|
+
}
|
|
111
|
+
currentStep = index - 1; // -1 because it will be incremented in the loop definition
|
|
112
|
+
},
|
|
113
|
+
beforeEach(callback) {
|
|
114
|
+
beforeEachCallback = callback;
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
async function runStep(step, messages) {
|
|
118
|
+
if (!apiKey) {
|
|
119
|
+
throw new Error('OpenAI API key is not set');
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
let response = null;
|
|
123
|
+
let prompt = typeof step.prompt === 'string' ? step.prompt : await step.prompt.content();
|
|
124
|
+
logger.debug('AI Core: context', step.context);
|
|
125
|
+
logger.debug('AI Core: messages', messages.toString(step.ignoreDirectives || false));
|
|
126
|
+
if (step.context) {
|
|
127
|
+
nunjucks_1.default.configure({ autoescape: true, trimBlocks: true, lstripBlocks: true });
|
|
128
|
+
prompt = nunjucks_1.default.renderString(prompt, step.context);
|
|
129
|
+
}
|
|
130
|
+
response = await runLLM(apiKey, prompt, messages.toString(step.ignoreDirectives || false), step.schema);
|
|
131
|
+
if (!response) {
|
|
132
|
+
throw new Error('No response from OpenAI');
|
|
133
|
+
}
|
|
134
|
+
logger.debug(`AI Core: response: ${response}`);
|
|
135
|
+
if (typeof step.shouldExecute === 'function') {
|
|
136
|
+
if (await step.shouldExecute(response)) {
|
|
137
|
+
logger.debug(`AI Core: executing`);
|
|
138
|
+
checkAttempts(step);
|
|
139
|
+
await step.execute(response);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
resetAttempts(step);
|
|
143
|
+
logger.debug(`AI Core: skipping`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
logger.debug(`AI Core: replying`);
|
|
148
|
+
await step.execute(response);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
// FIXME: this doesn't terminate the workflow
|
|
153
|
+
await step.onError(error.message);
|
|
154
|
+
shouldRun = false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function runDumbStep(step) {
|
|
158
|
+
try {
|
|
159
|
+
if (!step.runIf || (await step.runIf())) {
|
|
160
|
+
await step.execute();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
console.error(`AI Core: error in dumb step ${step.name}: ${error.message}`);
|
|
165
|
+
await step.onError(error.message);
|
|
166
|
+
shouldRun = false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function checkAttempts(step) {
|
|
170
|
+
if (step.maxAttempts) {
|
|
171
|
+
if (!attempts.has(step)) {
|
|
172
|
+
attempts.set(step, 0);
|
|
173
|
+
}
|
|
174
|
+
attempts.set(step, attempts.get(step) + 1);
|
|
175
|
+
if (attempts.get(step) > step.maxAttempts) {
|
|
176
|
+
throw new Error(`Max attempts reached for step ${step.name}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function resetAttempts(step) {
|
|
181
|
+
attempts.set(step, 0);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
async function runLLM(apiKey, systemPrompt, messages, schema) {
|
|
185
|
+
if (apiKey === '__TESTING__') {
|
|
186
|
+
await (0, core_1.sleep)(100);
|
|
187
|
+
return schema
|
|
188
|
+
? JSON.stringify({ message: 'canned response', reasons: [] })
|
|
189
|
+
: 'canned response';
|
|
190
|
+
}
|
|
191
|
+
const client = new openai_1.default({ apiKey });
|
|
192
|
+
logger.log('----------- RENDERED PROMPT ---------------');
|
|
193
|
+
logger.log(systemPrompt);
|
|
194
|
+
logger.log('---------------------------------------');
|
|
195
|
+
let format = { type: 'text' };
|
|
196
|
+
if (schema) {
|
|
197
|
+
format = {
|
|
198
|
+
type: 'json_schema',
|
|
199
|
+
json_schema: {
|
|
200
|
+
name: 'detector_response',
|
|
201
|
+
schema: (0, zod_to_json_schema_1.zodToJsonSchema)(schema),
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const response = await client.chat.completions.create({
|
|
206
|
+
messages: [
|
|
207
|
+
{ role: 'system', content: systemPrompt },
|
|
208
|
+
{ role: 'user', content: messages },
|
|
209
|
+
],
|
|
210
|
+
model: 'gpt-4o',
|
|
211
|
+
response_format: format,
|
|
212
|
+
temperature: 0.1,
|
|
213
|
+
});
|
|
214
|
+
if (!response.choices[0].message.content) {
|
|
215
|
+
throw new Error('No response from OpenAI');
|
|
216
|
+
}
|
|
217
|
+
return response.choices[0].message.content;
|
|
218
|
+
}
|
|
219
|
+
function loadFile(path) {
|
|
220
|
+
// NOTE: there probably will be S3 loading stuff here
|
|
221
|
+
return {
|
|
222
|
+
content: async () => {
|
|
223
|
+
logger.debug('AI Core: loading prompt:', path);
|
|
224
|
+
return fs_1.default.promises.readFile((0, path_1.join)(basePath, path), 'utf-8');
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
createWorkflow: createWorkflow,
|
|
230
|
+
createStep,
|
|
231
|
+
loadFile,
|
|
232
|
+
makeMessagesList,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface Action {
|
|
2
|
+
id: string;
|
|
3
|
+
type: string;
|
|
4
|
+
state: 'started' | 'completed' | 'failed';
|
|
5
|
+
message: string;
|
|
6
|
+
}
|
|
7
|
+
export type SendAction = (action: Action) => Promise<void>;
|
|
8
|
+
export declare function makeActionWrapper(sendAction: SendAction): (message: string, type: string, action: () => Promise<unknown>) => Promise<void>;
|
|
9
|
+
export declare function makeAction(sendAction: SendAction | undefined, type: string, message: string): (state?: Action["state"]) => Promise<void>;
|
|
10
|
+
//# sourceMappingURL=action.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action.d.ts","sourceRoot":"","sources":["../../../src/lib/bosun/action.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,MAAM;IACnB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAA;IACzC,OAAO,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;AAE1D,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,UAAU,aAEvC,MAAM,QACT,MAAM,UACJ,MAAM,OAAO,CAAC,OAAO,CAAC,mBAiBrC;AAED,wBAAgB,UAAU,CAAC,UAAU,EAAE,UAAU,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,YAM1D,MAAM,CAAC,OAAO,CAAC,mBAQhD"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.makeActionWrapper = makeActionWrapper;
|
|
4
|
+
exports.makeAction = makeAction;
|
|
5
|
+
function makeActionWrapper(sendAction) {
|
|
6
|
+
return async function wrapAction(message, type, action) {
|
|
7
|
+
const id = Math.random().toString();
|
|
8
|
+
await sendAction({
|
|
9
|
+
id,
|
|
10
|
+
type,
|
|
11
|
+
state: 'started',
|
|
12
|
+
message,
|
|
13
|
+
});
|
|
14
|
+
await action();
|
|
15
|
+
await sendAction({
|
|
16
|
+
id,
|
|
17
|
+
type,
|
|
18
|
+
state: 'completed',
|
|
19
|
+
message,
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function makeAction(sendAction, type, message) {
|
|
24
|
+
if (!sendAction) {
|
|
25
|
+
// noop
|
|
26
|
+
return async function action() { };
|
|
27
|
+
}
|
|
28
|
+
const id = Math.random().toString();
|
|
29
|
+
return function action(state = 'completed') {
|
|
30
|
+
return sendAction({
|
|
31
|
+
id,
|
|
32
|
+
type,
|
|
33
|
+
state,
|
|
34
|
+
message,
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { AiEngine } from '../ai';
|
|
2
|
+
import { Logger, Message, Scheduler } from '../interfaces';
|
|
3
|
+
import { SendAction } from './action';
|
|
4
|
+
import { Context } from './context';
|
|
5
|
+
type DefaultContext = Record<string, any>;
|
|
6
|
+
export interface TesAgentFactoryProps<CTX extends DefaultContext = DefaultContext> {
|
|
7
|
+
logger: Logger;
|
|
8
|
+
scheduler: Scheduler;
|
|
9
|
+
ai: AiEngine;
|
|
10
|
+
getMessages: () => Message[];
|
|
11
|
+
sendMessage: (message: string) => Promise<void>;
|
|
12
|
+
sendAction: SendAction;
|
|
13
|
+
ctx: Context<CTX>;
|
|
14
|
+
}
|
|
15
|
+
export interface TestAgent {
|
|
16
|
+
start: () => Promise<unknown>;
|
|
17
|
+
reactOnMessage: () => Promise<unknown>;
|
|
18
|
+
respondToMessage: () => Promise<unknown>;
|
|
19
|
+
isAssigned: () => Promise<boolean>;
|
|
20
|
+
onFatalError: (error: Error) => Promise<unknown>;
|
|
21
|
+
}
|
|
22
|
+
export type TestAgentFactory<T extends DefaultContext = DefaultContext> = (props: TesAgentFactoryProps<T>) => TestAgent;
|
|
23
|
+
export declare function createTestAgentFactory<T extends DefaultContext>(creator: TestAgentFactory<T>): TestAgentFactory<T>;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../../src/lib/bosun/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAChC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC,KAAK,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;AACzC,MAAM,WAAW,oBAAoB,CAAC,GAAG,SAAS,cAAc,GAAG,cAAc;IAC7E,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,SAAS,CAAA;IACpB,EAAE,EAAE,QAAQ,CAAA;IACZ,WAAW,EAAE,MAAM,OAAO,EAAE,CAAA;IAC5B,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/C,UAAU,EAAE,UAAU,CAAA;IACtB,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,CAAA;CACpB;AAED,MAAM,WAAW,SAAS;IACtB,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IAC7B,cAAc,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IACtC,gBAAgB,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IACxC,UAAU,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IAClC,YAAY,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CACnD;AAED,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc,IAAI,CACtE,KAAK,EAAE,oBAAoB,CAAC,CAAC,CAAC,KAC7B,SAAS,CAAA;AAEd,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,cAAc,EAAE,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,uBAE5F"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
type Ctx = Record<string, any>;
|
|
2
|
+
type Paths<T> = T extends object ? {
|
|
3
|
+
[K in keyof T]: [K] | [K, ...Paths<T[K]>];
|
|
4
|
+
}[keyof T] : never;
|
|
5
|
+
type PathValue<T, P extends any[]> = P extends [infer First, ...infer Rest] ? First extends keyof T ? Rest['length'] extends 0 ? T[First] : PathValue<T[First], Rest> : never : never;
|
|
6
|
+
declare class Context<T extends Ctx> {
|
|
7
|
+
#private;
|
|
8
|
+
constructor(context: T);
|
|
9
|
+
set<P extends Paths<T>>(path: P extends any[] ? P : never, value: PathValue<T, P>): void;
|
|
10
|
+
set(newContext: T): void;
|
|
11
|
+
get(): T;
|
|
12
|
+
/** silently swap the context */
|
|
13
|
+
swap(newContext: T): void;
|
|
14
|
+
subscribe: (listener: (context: T) => void) => () => boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function createContext<T extends Ctx>(context: T): Context<T>;
|
|
17
|
+
type Public<T> = {
|
|
18
|
+
[P in keyof T]: T[P];
|
|
19
|
+
};
|
|
20
|
+
type PublicContext<T extends Ctx> = Public<Context<T>>;
|
|
21
|
+
export type { PublicContext as Context };
|
|
22
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/lib/bosun/context.ts"],"names":[],"mappings":"AAAA,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;AAC9B,KAAK,KAAK,CAAC,CAAC,IAAI,CAAC,SAAS,MAAM,GAC1B;KACK,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAC5C,CAAC,MAAM,CAAC,CAAC,GACV,KAAK,CAAA;AAEX,KAAK,SAAS,CAAC,CAAC,EAAE,CAAC,SAAS,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,GACrE,KAAK,SAAS,MAAM,CAAC,GACjB,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,GACpB,CAAC,CAAC,KAAK,CAAC,GACR,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,GAC7B,KAAK,GACT,KAAK,CAAA;AAEX,cAAM,OAAO,CAAC,CAAC,SAAS,GAAG;;gBAGX,OAAO,EAAE,CAAC;IAItB,GAAG,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,SAAS,GAAG,EAAE,GAAG,CAAC,GAAG,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI;IACxF,GAAG,CAAC,UAAU,EAAE,CAAC,GAAG,IAAI;IAmBxB,GAAG;IAIH,gCAAgC;IAChC,IAAI,CAAC,UAAU,EAAE,CAAC;IAIlB,SAAS,aAAc,CAAC,OAAO,EAAE,CAAC,KAAK,IAAI,mBAG1C;CACJ;AAED,wBAAgB,aAAa,CAAC,CAAC,SAAS,GAAG,EAAE,OAAO,EAAE,CAAC,cAEtD;AAED,KAAK,MAAM,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAE,CAAA;AAEzC,KAAK,aAAa,CAAC,CAAC,SAAS,GAAG,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;AAEtD,YAAY,EAAE,aAAa,IAAI,OAAO,EAAE,CAAA"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createContext = createContext;
|
|
4
|
+
class Context {
|
|
5
|
+
#listeners = new Set();
|
|
6
|
+
#context;
|
|
7
|
+
constructor(context) {
|
|
8
|
+
this.#context = context;
|
|
9
|
+
}
|
|
10
|
+
set(pathOrContext, value) {
|
|
11
|
+
if (!this.#context) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (!Array.isArray(pathOrContext)) {
|
|
15
|
+
this.#context = pathOrContext;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
const path = pathOrContext;
|
|
19
|
+
let current = this.#context;
|
|
20
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
21
|
+
current = current[path[i]];
|
|
22
|
+
}
|
|
23
|
+
// @ts-ignore
|
|
24
|
+
current[path[path.length - 1]] = value;
|
|
25
|
+
}
|
|
26
|
+
this.#listeners.forEach((listener) => listener(this.#context));
|
|
27
|
+
}
|
|
28
|
+
get() {
|
|
29
|
+
return this.#context;
|
|
30
|
+
}
|
|
31
|
+
/** silently swap the context */
|
|
32
|
+
swap(newContext) {
|
|
33
|
+
this.#context = newContext;
|
|
34
|
+
}
|
|
35
|
+
subscribe = (listener) => {
|
|
36
|
+
this.#listeners.add(listener);
|
|
37
|
+
return () => this.#listeners.delete(listener);
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function createContext(context) {
|
|
41
|
+
return new Context(context);
|
|
42
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/bosun/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA;AACxB,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,QAAQ,CAAA"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./action"), exports);
|
|
18
|
+
__exportStar(require("./context"), exports);
|
|
19
|
+
__exportStar(require("./agent"), exports);
|
|
20
|
+
__exportStar(require("./mock"), exports);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Logger } from '../interfaces';
|
|
2
|
+
type ConstructorType<T> = new (...args: any[]) => T;
|
|
3
|
+
type InstanceType<T extends ConstructorType<any>> = T extends new (...args: any[]) => infer R ? R : never;
|
|
4
|
+
export declare function createMock<T extends ConstructorType<any>>(logger: Logger, constructor: T, overrides?: Partial<InstanceType<T>>): InstanceType<T>;
|
|
5
|
+
export {};
|
|
6
|
+
//# sourceMappingURL=mock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock.d.ts","sourceRoot":"","sources":["../../../src/lib/bosun/mock.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAEtC,KAAK,eAAe,CAAC,CAAC,IAAI,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAA;AACnD,KAAK,YAAY,CAAC,CAAC,SAAS,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,MAAM,CAAC,GACvF,CAAC,GACD,KAAK,CAAA;AAEX,wBAAgB,UAAU,CAAC,CAAC,SAAS,eAAe,CAAC,GAAG,CAAC,EACrD,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,CAAC,EACd,SAAS,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,GACrC,YAAY,CAAC,CAAC,CAAC,CAuBjB"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createMock = createMock;
|
|
4
|
+
const node_util_1 = require("node:util");
|
|
5
|
+
function createMock(logger, constructor, overrides) {
|
|
6
|
+
const mock = Object.create(constructor.prototype);
|
|
7
|
+
const properties = Object.getOwnPropertyDescriptors(constructor.prototype);
|
|
8
|
+
for (const [key, descriptor] of Object.entries(properties)) {
|
|
9
|
+
if (typeof descriptor.value === 'function' && key !== 'constructor') {
|
|
10
|
+
mock[key] = function (...args) {
|
|
11
|
+
let returnValue = {};
|
|
12
|
+
if (overrides && key in overrides && typeof overrides[key] === 'function') {
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
14
|
+
returnValue = overrides[key](...args);
|
|
15
|
+
}
|
|
16
|
+
const logs = [`${constructor.name}.${key}\n\tCalled with:`, prettyArgs(args)];
|
|
17
|
+
if (returnValue) {
|
|
18
|
+
logs.push(`\n\tReturning: ${prettyInspect(returnValue)}`);
|
|
19
|
+
}
|
|
20
|
+
logger.log(...logs);
|
|
21
|
+
return returnValue;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return mock;
|
|
26
|
+
}
|
|
27
|
+
function prettyArgs(args) {
|
|
28
|
+
return args.map((arg) => (typeof arg === 'object' ? prettyInspect(arg) : arg)).join(' ');
|
|
29
|
+
}
|
|
30
|
+
function prettyInspect(obj) {
|
|
31
|
+
return (0, node_util_1.inspect)(obj, { depth: null }).split('\n').join('\n\t');
|
|
32
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { AiEngine } from '../ai';
|
|
2
|
+
import { Logger, Message, Scheduler } from '../interfaces';
|
|
3
|
+
type DefaultContext = Record<string, any>;
|
|
4
|
+
export interface TestWorkflowFactoryProps<CTX extends DefaultContext = DefaultContext> {
|
|
5
|
+
logger: Logger;
|
|
6
|
+
scheduler: Scheduler;
|
|
7
|
+
ai: AiEngine;
|
|
8
|
+
getMessages: (workflow: string) => Message[];
|
|
9
|
+
sendMessage: (workflow: string, message: string) => Promise<void>;
|
|
10
|
+
sendAction: ctx;
|
|
11
|
+
}
|
|
12
|
+
export interface TestWorkflow {
|
|
13
|
+
start: () => Promise<unknown>;
|
|
14
|
+
reactOnMessage: () => Promise<unknown>;
|
|
15
|
+
respondToMessage: () => Promise<unknown>;
|
|
16
|
+
isAssigned: () => Promise<boolean>;
|
|
17
|
+
onFatalError: (error: Error) => Promise<unknown>;
|
|
18
|
+
getContext: () => DefaultContext;
|
|
19
|
+
setContext: (context: DefaultContext) => void;
|
|
20
|
+
}
|
|
21
|
+
export type TestWorkflowFactory = (props: TestWorkflowFactoryProps) => Record<string, TestWorkflow>;
|
|
22
|
+
export declare function createTestWorkflowFactory(creator: TestWorkflowFactory): TestWorkflowFactory;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=test-workflow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-workflow.d.ts","sourceRoot":"","sources":["../../../src/lib/bosun/test-workflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAChC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAE1D,KAAK,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;AACzC,MAAM,WAAW,wBAAwB,CAAC,GAAG,SAAS,cAAc,GAAG,cAAc;IACjF,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,SAAS,CAAA;IACpB,EAAE,EAAE,QAAQ,CAAA;IACZ,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,EAAE,CAAA;IAC5C,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACjE,UAAU,EACV,GAAG,CAAA;CAAC;AAOR,MAAM,WAAW,YAAY;IAEzB,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IAC7B,cAAc,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IACtC,gBAAgB,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IACxC,UAAU,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IAClC,YAAY,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAEhD,UAAU,EAAE,MAAM,cAAc,CAAA;IAChC,UAAU,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAA;CAChD;AAED,MAAM,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,wBAAwB,KAAK,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;AAEnG,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,mBAAmB,uBAErE"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createTestWorkflowFactory = createTestWorkflowFactory;
|
|
4
|
+
{
|
|
5
|
+
get: () => CTX;
|
|
6
|
+
set: (ctx) => void subscribe;
|
|
7
|
+
(cb) => void ;
|
|
8
|
+
}
|
|
9
|
+
function createTestWorkflowFactory(creator) {
|
|
10
|
+
return creator;
|
|
11
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { AiEngine } from '../ai';
|
|
2
|
+
import { Logger, Message, Scheduler } from '../interfaces';
|
|
3
|
+
import { SendAction } from './action';
|
|
4
|
+
import { Context } from './context';
|
|
5
|
+
type DefaultContext = Record<string, any>;
|
|
6
|
+
export interface TestWorkflowFactoryProps<CTX extends DefaultContext = DefaultContext> {
|
|
7
|
+
logger: Logger;
|
|
8
|
+
scheduler: Scheduler;
|
|
9
|
+
ai: AiEngine;
|
|
10
|
+
getMessages: () => Message[];
|
|
11
|
+
sendMessage: (message: string) => Promise<void>;
|
|
12
|
+
sendAction: SendAction;
|
|
13
|
+
ctx: Context<CTX>;
|
|
14
|
+
}
|
|
15
|
+
export interface TestWorkflow {
|
|
16
|
+
start: () => Promise<unknown>;
|
|
17
|
+
reactOnMessage: () => Promise<unknown>;
|
|
18
|
+
respondToMessage: () => Promise<unknown>;
|
|
19
|
+
isAssigned: () => Promise<boolean>;
|
|
20
|
+
onFatalError: (error: Error) => Promise<unknown>;
|
|
21
|
+
}
|
|
22
|
+
export type TestWorkflowFactory<T extends DefaultContext = DefaultContext> = (props: TestWorkflowFactoryProps<T>) => TestWorkflow;
|
|
23
|
+
export declare function createTestWorkflowFactory<T extends DefaultContext>(creator: TestWorkflowFactory<T>): TestWorkflowFactory<T>;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=workflow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow.d.ts","sourceRoot":"","sources":["../../../src/lib/bosun/workflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAChC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC,KAAK,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;AACzC,MAAM,WAAW,wBAAwB,CAAC,GAAG,SAAS,cAAc,GAAG,cAAc;IACjF,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,SAAS,CAAA;IACpB,EAAE,EAAE,QAAQ,CAAA;IACZ,WAAW,EAAE,MAAM,OAAO,EAAE,CAAA;IAC5B,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/C,UAAU,EAAE,UAAU,CAAA;IACtB,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,CAAA;CACpB;AAED,MAAM,WAAW,YAAY;IACzB,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IAC7B,cAAc,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IACtC,gBAAgB,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IACxC,UAAU,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IAClC,YAAY,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CACnD;AAED,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc,IAAI,CACzE,KAAK,EAAE,wBAAwB,CAAC,CAAC,CAAC,KACjC,YAAY,CAAA;AAEjB,wBAAgB,yBAAyB,CAAC,CAAC,SAAS,cAAc,EAC9D,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,0BAGlC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface Logger {
|
|
2
|
+
log: (...args: any[]) => void;
|
|
3
|
+
debug: (...args: any[]) => void;
|
|
4
|
+
error: (...args: any[]) => void;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* A function that schedules an action to be executed in the future
|
|
8
|
+
* @param delay – a date when the action should be executed use {@link delayFactory} to create a
|
|
9
|
+
* date
|
|
10
|
+
* @param phone – user's phone
|
|
11
|
+
*/
|
|
12
|
+
type ScheduleAction = (delay: Date, phone: string) => Promise<void>;
|
|
13
|
+
export interface Scheduler {
|
|
14
|
+
/**
|
|
15
|
+
* Register a delayed action handler.
|
|
16
|
+
* @param actionName – a unique (inside one use-case) name for the action
|
|
17
|
+
* @param action – a function that will be called when the action is triggered
|
|
18
|
+
* @returns a function to schedule the action
|
|
19
|
+
*/
|
|
20
|
+
registerAction: (actionName: string, action: (phone: string) => Promise<unknown>) => ScheduleAction;
|
|
21
|
+
/**
|
|
22
|
+
* Removes all actions for the given phone that were not executed yet.
|
|
23
|
+
*/
|
|
24
|
+
clearAllPendingActions: (phone: string) => Promise<unknown>;
|
|
25
|
+
}
|
|
26
|
+
export interface Message {
|
|
27
|
+
sender: 'user' | 'agent' | 'system';
|
|
28
|
+
text: string;
|
|
29
|
+
imageUrl?: string;
|
|
30
|
+
}
|
|
31
|
+
export {};
|
|
32
|
+
//# sourceMappingURL=interfaces.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../../src/lib/interfaces.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,MAAM;IACnB,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IAC7B,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IAC/B,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;CAClC;AAED;;;;;GAKG;AACH,KAAK,cAAc,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;AAEnE,MAAM,WAAW,SAAS;IACtB;;;;;OAKG;IACH,cAAc,EAAE,CACZ,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,KAC1C,cAAc,CAAA;IAEnB;;OAEG;IACH,sBAAsB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CAC9D;AAED,MAAM,WAAW,OAAO;IACpB,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAA;IACnC,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @returns getNextTimePoint function that makes a date from a given {@link Delay}
|
|
3
|
+
* - note that Delay must not be negative
|
|
4
|
+
*/
|
|
5
|
+
export declare function delayFactory(schedule: Schedule): (delay: Delay) => Date;
|
|
6
|
+
type Delay = [number, 'minutes' | 'hours' | 'days'] | [number, 'hours', number, 'minutes'] | [number, 'days', number, 'hours'] | [number, 'days', number, 'hours', number, 'minutes'];
|
|
7
|
+
type singleInt = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
|
|
8
|
+
type hours = `${0 | 1}${singleInt}` | '20' | '21' | '22' | '23';
|
|
9
|
+
type Time = `${hours}:${0 | 1 | 2 | 3 | 4 | 5}${singleInt}`;
|
|
10
|
+
type WeekDays = 'monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday' | 'saturday' | 'sunday';
|
|
11
|
+
type Year = 2024 | 2025 | 2026 | 2027 | 2028 | 2029 | 2030;
|
|
12
|
+
type Month = '01' | '02' | '03' | '04' | '05' | '06' | '07' | '08' | '09' | '10' | '11' | '12';
|
|
13
|
+
type DateFmt = `${Year}-${Month}-${0 | 1 | 2 | 3}${singleInt}`;
|
|
14
|
+
interface TimeRange {
|
|
15
|
+
days: WeekDays[];
|
|
16
|
+
from: Time;
|
|
17
|
+
to: Time;
|
|
18
|
+
}
|
|
19
|
+
export interface Schedule {
|
|
20
|
+
timeZone: string;
|
|
21
|
+
workingHours?: TimeRange[];
|
|
22
|
+
holidays?: DateFmt[];
|
|
23
|
+
}
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=schedule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schedule.d.ts","sourceRoot":"","sources":["../../src/lib/schedule.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,WACH,KAAK,KAAG,IAAI,CAwCvD;AAkFD,KAAK,KAAK,GACJ,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC,GACtC,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,GACpC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,GACjC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,CAAA;AAE1D,KAAK,SAAS,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;AACtD,KAAK,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,SAAS,EAAE,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAC/D,KAAK,IAAI,GAAG,GAAG,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,SAAS,EAAE,CAAA;AAC3D,KAAK,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,QAAQ,CAAA;AAClG,KAAK,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAC1D,KAAK,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAE9F,KAAK,OAAO,GAAG,GAAG,IAAI,IAAI,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,SAAS,EAAE,CAAA;AAE9D,UAAU,SAAS;IACf,IAAI,EAAE,QAAQ,EAAE,CAAA;IAChB,IAAI,EAAE,IAAI,CAAA;IACV,EAAE,EAAE,IAAI,CAAA;CACX;AAED,MAAM,WAAW,QAAQ;IACrB,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,SAAS,EAAE,CAAA;IAC1B,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAA;CACvB"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.delayFactory = delayFactory;
|
|
4
|
+
/**
|
|
5
|
+
* @returns getNextTimePoint function that makes a date from a given {@link Delay}
|
|
6
|
+
* - note that Delay must not be negative
|
|
7
|
+
*/
|
|
8
|
+
function delayFactory(schedule) {
|
|
9
|
+
return function getNextTimePoint(delay) {
|
|
10
|
+
const date = new Date();
|
|
11
|
+
const { holidays, timeZone, workingHours } = schedule;
|
|
12
|
+
const delayMs = getDelayTimeInMs(delay);
|
|
13
|
+
const [_, offsetDiffBefore] = convertToTimeZone(date, timeZone);
|
|
14
|
+
const localOffsetBefore = date.getTimezoneOffset() * 60 * 1000;
|
|
15
|
+
date.setTime(date.getTime() + delayMs);
|
|
16
|
+
let [targetTzDate, offsetDiff] = convertToTimeZone(date, timeZone);
|
|
17
|
+
const localOffsetAfterMs = targetTzDate.getTimezoneOffset() * 60 * 1000;
|
|
18
|
+
const localOffsetChange = localOffsetAfterMs - localOffsetBefore;
|
|
19
|
+
const targetTzOffsetChange = offsetDiff - offsetDiffBefore;
|
|
20
|
+
if (workingHours && workingHours?.length > 0) {
|
|
21
|
+
targetTzDate = getNextWorkingDate(targetTzDate, workingHours);
|
|
22
|
+
}
|
|
23
|
+
if (holidays && isHoliday(targetTzDate, holidays)) {
|
|
24
|
+
targetTzDate = skipHoliday(targetTzDate, holidays);
|
|
25
|
+
}
|
|
26
|
+
const targetDate = new Date(targetTzDate.getTime() + offsetDiff + targetTzOffsetChange + localOffsetChange);
|
|
27
|
+
const [, offsetDiffTz] = convertToTimeZone(targetDate, timeZone);
|
|
28
|
+
const targetOffsetTzChange = offsetDiffTz - offsetDiff;
|
|
29
|
+
return new Date(targetTzDate.getTime() +
|
|
30
|
+
offsetDiff +
|
|
31
|
+
targetTzOffsetChange +
|
|
32
|
+
localOffsetChange +
|
|
33
|
+
targetOffsetTzChange);
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function getDelayTimeInMs(time) {
|
|
37
|
+
const minutesIndex = time.findIndex((el) => el === 'minutes');
|
|
38
|
+
const minutes = minutesIndex !== -1 ? time[minutesIndex - 1] : 0;
|
|
39
|
+
const hoursIndex = time.findIndex((el) => el === 'hours');
|
|
40
|
+
const hours = hoursIndex !== -1 ? time[hoursIndex - 1] : 0;
|
|
41
|
+
const daysIndex = time.findIndex((el) => el === 'days');
|
|
42
|
+
const days = daysIndex !== -1 ? time[daysIndex - 1] : 0;
|
|
43
|
+
if ([days, hours, minutes].some((v) => v < 0)) {
|
|
44
|
+
throw new Error('Negative delay');
|
|
45
|
+
}
|
|
46
|
+
return (minutes * 60 + hours * 60 * 60 + days * 24 * 60 * 60) * 1000;
|
|
47
|
+
}
|
|
48
|
+
function convertToTimeZone(date, timeZone) {
|
|
49
|
+
const convertedDate = new Date(date.toLocaleString('en-US', { timeZone }));
|
|
50
|
+
const offsetMS = date.getTime() - convertedDate.getTime();
|
|
51
|
+
return [convertedDate, offsetMS];
|
|
52
|
+
}
|
|
53
|
+
function getNextWorkingDate(date, workingHours) {
|
|
54
|
+
const currentDate = new Date(date);
|
|
55
|
+
while (true) {
|
|
56
|
+
const currentDay = getDayOfWeek(currentDate);
|
|
57
|
+
const daySchedule = workingHours.find((interval) => interval.days.includes(currentDay));
|
|
58
|
+
if (daySchedule) {
|
|
59
|
+
const [fromHours, fromMinutes] = daySchedule.from.split(':').map(Number);
|
|
60
|
+
const [toHours, toMinutes] = daySchedule.to.split(':').map(Number);
|
|
61
|
+
if (fromHours > toHours) {
|
|
62
|
+
throw new Error('"from" is later then "to"');
|
|
63
|
+
}
|
|
64
|
+
const startTime = new Date(currentDate);
|
|
65
|
+
startTime.setHours(fromHours, fromMinutes, 0, 0);
|
|
66
|
+
const endTime = new Date(currentDate);
|
|
67
|
+
endTime.setHours(toHours, toMinutes, 0, 0);
|
|
68
|
+
if (currentDate >= startTime && currentDate <= endTime) {
|
|
69
|
+
return currentDate;
|
|
70
|
+
}
|
|
71
|
+
else if (currentDate < startTime) {
|
|
72
|
+
return startTime;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
currentDate.setDate(currentDate.getDate() + 1);
|
|
76
|
+
currentDate.setHours(0, 0, 0, 0);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function getDayOfWeek(date) {
|
|
80
|
+
const days = [
|
|
81
|
+
'sunday',
|
|
82
|
+
'monday',
|
|
83
|
+
'tuesday',
|
|
84
|
+
'wednesday',
|
|
85
|
+
'thursday',
|
|
86
|
+
'friday',
|
|
87
|
+
'saturday',
|
|
88
|
+
];
|
|
89
|
+
return days[date.getDay()];
|
|
90
|
+
}
|
|
91
|
+
function isHoliday(date, holidays) {
|
|
92
|
+
const dateString = date.toISOString().split('T')[0];
|
|
93
|
+
return holidays.includes(dateString);
|
|
94
|
+
}
|
|
95
|
+
function skipHoliday(date, holidays) {
|
|
96
|
+
while (isHoliday(date, holidays)) {
|
|
97
|
+
date.setDate(date.getDate() + 1);
|
|
98
|
+
}
|
|
99
|
+
return date;
|
|
100
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { AiEngine } from './ai';
|
|
2
|
+
import { Logger, Message, Scheduler } from './interfaces';
|
|
3
|
+
type DefaultContext = Record<string, any>;
|
|
4
|
+
export interface TestWorkflowFactoryProps<CTX extends DefaultContext = DefaultContext> {
|
|
5
|
+
logger: Logger;
|
|
6
|
+
scheduler: Scheduler;
|
|
7
|
+
ai: AiEngine;
|
|
8
|
+
getMessages: (workflow: string) => Message[];
|
|
9
|
+
sendMessage: (workflow: string, message: string) => Promise<void>;
|
|
10
|
+
sendAction: ctx;
|
|
11
|
+
}
|
|
12
|
+
export interface TestWorkflow {
|
|
13
|
+
start: () => Promise<unknown>;
|
|
14
|
+
reactOnMessage: () => Promise<unknown>;
|
|
15
|
+
respondToMessage: () => Promise<unknown>;
|
|
16
|
+
isAssigned: () => Promise<boolean>;
|
|
17
|
+
onFatalError: (error: Error) => Promise<unknown>;
|
|
18
|
+
getContext: () => DefaultContext;
|
|
19
|
+
setContext: (context: DefaultContext) => void;
|
|
20
|
+
}
|
|
21
|
+
export type TestWorkflowFactory = (props: TestWorkflowFactoryProps) => Record<string, TestWorkflow>;
|
|
22
|
+
export declare function createTestWorkflowFactory(creator: TestWorkflowFactory): TestWorkflowFactory;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=test-workflow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-workflow.d.ts","sourceRoot":"","sources":["../../src/lib/test-workflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAA;AAC/B,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAEzD,KAAK,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;AACzC,MAAM,WAAW,wBAAwB,CAAC,GAAG,SAAS,cAAc,GAAG,cAAc;IACjF,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,SAAS,CAAA;IACpB,EAAE,EAAE,QAAQ,CAAA;IACZ,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,EAAE,CAAA;IAC5C,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACjE,UAAU,EACV,GAAG,CAAA;CAAC;AAOR,MAAM,WAAW,YAAY;IAEzB,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IAC7B,cAAc,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IACtC,gBAAgB,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IACxC,UAAU,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IAClC,YAAY,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAEhD,UAAU,EAAE,MAAM,cAAc,CAAA;IAChC,UAAU,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAA;CAChD;AAED,MAAM,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,wBAAwB,KAAK,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;AAEnG,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,mBAAmB,uBAErE"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createTestWorkflowFactory = createTestWorkflowFactory;
|
|
4
|
+
{
|
|
5
|
+
get: () => CTX;
|
|
6
|
+
set: (ctx) => void subscribe;
|
|
7
|
+
(cb) => void ;
|
|
8
|
+
}
|
|
9
|
+
function createTestWorkflowFactory(creator) {
|
|
10
|
+
return creator;
|
|
11
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@recombine-ai/engine",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Recombine AI engine for creating conversational AI agents",
|
|
5
|
+
"main": "build/index.js",
|
|
6
|
+
"types": "build/index.d.ts",
|
|
7
|
+
"author": "Recombine AI limited",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"private": false,
|
|
10
|
+
"scripts": {
|
|
11
|
+
"dev": "tsc -w",
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"prepublishOnly": "npm run build",
|
|
14
|
+
"test": "vitest"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/node": "^22.8.1",
|
|
18
|
+
"@types/nunjucks": "^3.2.6",
|
|
19
|
+
"prettier": "^3.3.3",
|
|
20
|
+
"typescript": "^5.7.3",
|
|
21
|
+
"vitest": "^3.0.6"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"nunjucks": "^3.2.4",
|
|
25
|
+
"openai": "^4.68.4",
|
|
26
|
+
"zod": "^3.23.8",
|
|
27
|
+
"zod-to-json-schema": "^3.23.5"
|
|
28
|
+
}
|
|
29
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
<!-- cspell:words agentic -->
|
|
2
|
+
|
|
3
|
+
# Recombine AI Engine
|
|
4
|
+
|
|
5
|
+
A TypeScript library for building agentic workflows for conversational AI.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- 🔄 Multi-step agentic workflows
|
|
10
|
+
- 🎯 Conditional execution and reviewers
|
|
11
|
+
- 📝 Structured responses using Zod schemas
|
|
12
|
+
- 🗂️ File-based prompts
|
|
13
|
+
- ⚡ Message history management
|
|
14
|
+
- 🌍 Context injection using Nunjucks templates
|
|
15
|
+
- 👩💻 Ready to be integrated with Recombine Bosun prompt-engineering IDE.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npm install @recombine/ai-engine
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Basic Usage
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { createAIEngine } from '@recombine-ai/engine'
|
|
27
|
+
|
|
28
|
+
const engine = createAIEngine({
|
|
29
|
+
basePath: './path/to/prompts',
|
|
30
|
+
})
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Creating a Workflow
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
// Create message list
|
|
37
|
+
const messages = engine.makeMessagesList()
|
|
38
|
+
messages.addMessage('User', 'Hello!')
|
|
39
|
+
|
|
40
|
+
// Define steps
|
|
41
|
+
const mainStep = engine.createStep({
|
|
42
|
+
name: 'mainStep',
|
|
43
|
+
prompt: engine.loadFile('conversation/main.txt'),
|
|
44
|
+
context: { userName: 'John Doe' },
|
|
45
|
+
execute: async (response) => {
|
|
46
|
+
messages.setProposedReply(response)
|
|
47
|
+
},
|
|
48
|
+
onError: async (error) => {
|
|
49
|
+
console.error('Error:', error)
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const myReviewer = engine.createStep({
|
|
54
|
+
/* ... */
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const myCoordinator = engine.createStep({
|
|
58
|
+
/* ... */
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
// Create and run workflow
|
|
62
|
+
const workflow = await engine.createWorkflow(mainStep, myReviewer, myCoordinator)
|
|
63
|
+
const response = await workflow.run(messages)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Main concepts
|
|
67
|
+
|
|
68
|
+
### Workflow & Steps
|
|
69
|
+
|
|
70
|
+
AI agentic workflow is a chain of prompts where one prompt handles results of another and either
|
|
71
|
+
does some side-effects or changes those results.
|
|
72
|
+
|
|
73
|
+
In Recombine Engine we define these prompts as _steps_. Each step consists of a prompt and a bunch
|
|
74
|
+
of related configurations and supporting functions, that e.g. determine whether a side-effect should
|
|
75
|
+
happen and what should happen int those side-effects.
|
|
76
|
+
|
|
77
|
+
#### Step Configuration
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
const myStep = engine.createStep({
|
|
81
|
+
name: 'myStep', // Step identifier used for observability
|
|
82
|
+
prompt: 'Here goes your prompt', // Prompt text or file, loaded with engine.loadFile('path')
|
|
83
|
+
schema: zod.object({/* ... */}), // Structured response schema
|
|
84
|
+
context: {userName: 'John Doe'}, // Variables to be used in prompts
|
|
85
|
+
ignoreDirectives: false, // Do not add directives into messages log
|
|
86
|
+
runIf: (messages: Messages) => true, // Run this step or skip it completely
|
|
87
|
+
shouldExecute: (reply: string) => true, // Execute side-effects, if needed
|
|
88
|
+
execute: async (reply: string) => {/* ... */}, // Side effect definition
|
|
89
|
+
onError: (error: string) => {/* ... */}, // Handle error during step execution
|
|
90
|
+
maxAttempts: 3 // Max rewind attempts (for reviewers)
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
#### Workflow methods
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
const workflow = await engine.createWorkflow(/* workflow steps */)
|
|
98
|
+
|
|
99
|
+
const reply = await workflow.run(messages) // run the workflow to get the proposed reply at the end
|
|
100
|
+
workflow.terminate() // usually used in kill-switch or error handlers: terminates workflow, so no further steps will be executed
|
|
101
|
+
workflow.rewind(step) // restart workflow from a particular step
|
|
102
|
+
workflow.beforeEach(callback) // a callback to run before each step, e.g. to terminate workflow due to some external reasons
|
|
103
|
+
```
|