@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.
Files changed (38) hide show
  1. package/build/index.d.ts +5 -0
  2. package/build/index.d.ts.map +1 -0
  3. package/build/index.js +23 -0
  4. package/build/lib/ai.d.ts +88 -0
  5. package/build/lib/ai.d.ts.map +1 -0
  6. package/build/lib/ai.js +234 -0
  7. package/build/lib/bosun/action.d.ts +10 -0
  8. package/build/lib/bosun/action.d.ts.map +1 -0
  9. package/build/lib/bosun/action.js +37 -0
  10. package/build/lib/bosun/agent.d.ts +25 -0
  11. package/build/lib/bosun/agent.d.ts.map +1 -0
  12. package/build/lib/bosun/agent.js +6 -0
  13. package/build/lib/bosun/context.d.ts +22 -0
  14. package/build/lib/bosun/context.d.ts.map +1 -0
  15. package/build/lib/bosun/context.js +42 -0
  16. package/build/lib/bosun/index.d.ts +5 -0
  17. package/build/lib/bosun/index.d.ts.map +1 -0
  18. package/build/lib/bosun/index.js +20 -0
  19. package/build/lib/bosun/mock.d.ts +6 -0
  20. package/build/lib/bosun/mock.d.ts.map +1 -0
  21. package/build/lib/bosun/mock.js +32 -0
  22. package/build/lib/bosun/test-workflow.d.ts +24 -0
  23. package/build/lib/bosun/test-workflow.d.ts.map +1 -0
  24. package/build/lib/bosun/test-workflow.js +11 -0
  25. package/build/lib/bosun/workflow.d.ts +25 -0
  26. package/build/lib/bosun/workflow.d.ts.map +1 -0
  27. package/build/lib/bosun/workflow.js +6 -0
  28. package/build/lib/interfaces.d.ts +32 -0
  29. package/build/lib/interfaces.d.ts.map +1 -0
  30. package/build/lib/interfaces.js +2 -0
  31. package/build/lib/schedule.d.ts +25 -0
  32. package/build/lib/schedule.d.ts.map +1 -0
  33. package/build/lib/schedule.js +100 -0
  34. package/build/lib/test-workflow.d.ts +24 -0
  35. package/build/lib/test-workflow.d.ts.map +1 -0
  36. package/build/lib/test-workflow.js +11 -0
  37. package/package.json +29 -0
  38. package/readme.md +103 -0
@@ -0,0 +1,5 @@
1
+ export { type AiEngine, createAIEngine } from './lib/ai';
2
+ export { delayFactory, type Schedule } from './lib/schedule';
3
+ export * from './lib/interfaces';
4
+ export * from './lib/bosun';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -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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"}
@@ -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,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createTestAgentFactory = createTestAgentFactory;
4
+ function createTestAgentFactory(creator) {
5
+ return creator;
6
+ }
@@ -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,5 @@
1
+ export * from './action';
2
+ export * from './context';
3
+ export * from './agent';
4
+ export * from './mock';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -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,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createTestWorkflowFactory = createTestWorkflowFactory;
4
+ function createTestWorkflowFactory(creator) {
5
+ return creator;
6
+ }
@@ -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,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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
+ ```