@flomatai/core 0.1.0
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/dist/agent.d.ts +92 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +137 -0
- package/dist/agent.js.map +1 -0
- package/dist/cli-utils.d.ts +41 -0
- package/dist/cli-utils.d.ts.map +1 -0
- package/dist/cli-utils.js +64 -0
- package/dist/cli-utils.js.map +1 -0
- package/dist/errors.d.ts +52 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +105 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/llm-provider.d.ts +29 -0
- package/dist/llm-provider.d.ts.map +1 -0
- package/dist/llm-provider.js +44 -0
- package/dist/llm-provider.js.map +1 -0
- package/dist/logger.d.ts +32 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +75 -0
- package/dist/logger.js.map +1 -0
- package/dist/mock-llm.d.ts +70 -0
- package/dist/mock-llm.d.ts.map +1 -0
- package/dist/mock-llm.js +385 -0
- package/dist/mock-llm.js.map +1 -0
- package/dist/orchestrator-helpers.d.ts +20 -0
- package/dist/orchestrator-helpers.d.ts.map +1 -0
- package/dist/orchestrator-helpers.js +38 -0
- package/dist/orchestrator-helpers.js.map +1 -0
- package/dist/orchestrator.d.ts +124 -0
- package/dist/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator.js +349 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/pipeline-registry.d.ts +120 -0
- package/dist/pipeline-registry.d.ts.map +1 -0
- package/dist/pipeline-registry.js +171 -0
- package/dist/pipeline-registry.js.map +1 -0
- package/dist/pipeline.d.ts +122 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +152 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/skill.d.ts +112 -0
- package/dist/skill.d.ts.map +1 -0
- package/dist/skill.js +12 -0
- package/dist/skill.js.map +1 -0
- package/dist/skills/io-skill.d.ts +49 -0
- package/dist/skills/io-skill.d.ts.map +1 -0
- package/dist/skills/io-skill.js +103 -0
- package/dist/skills/io-skill.js.map +1 -0
- package/dist/skills/llm-skill.d.ts +64 -0
- package/dist/skills/llm-skill.d.ts.map +1 -0
- package/dist/skills/llm-skill.js +112 -0
- package/dist/skills/llm-skill.js.map +1 -0
- package/dist/skills/transform-skill.d.ts +27 -0
- package/dist/skills/transform-skill.d.ts.map +1 -0
- package/dist/skills/transform-skill.js +32 -0
- package/dist/skills/transform-skill.js.map +1 -0
- package/dist/state/file-store.d.ts +25 -0
- package/dist/state/file-store.d.ts.map +1 -0
- package/dist/state/file-store.js +92 -0
- package/dist/state/file-store.js.map +1 -0
- package/dist/state/memory-store.d.ts +24 -0
- package/dist/state/memory-store.d.ts.map +1 -0
- package/dist/state/memory-store.js +65 -0
- package/dist/state/memory-store.js.map +1 -0
- package/dist/state/types.d.ts +40 -0
- package/dist/state/types.d.ts.map +1 -0
- package/dist/state/types.js +8 -0
- package/dist/state/types.js.map +1 -0
- package/dist/strategies/custom.d.ts +12 -0
- package/dist/strategies/custom.d.ts.map +1 -0
- package/dist/strategies/custom.js +14 -0
- package/dist/strategies/custom.js.map +1 -0
- package/dist/strategies/plan-and-execute.d.ts +27 -0
- package/dist/strategies/plan-and-execute.d.ts.map +1 -0
- package/dist/strategies/plan-and-execute.js +195 -0
- package/dist/strategies/plan-and-execute.js.map +1 -0
- package/dist/strategies/react.d.ts +27 -0
- package/dist/strategies/react.d.ts.map +1 -0
- package/dist/strategies/react.js +172 -0
- package/dist/strategies/react.js.map +1 -0
- package/dist/strategies/router.d.ts +11 -0
- package/dist/strategies/router.d.ts.map +1 -0
- package/dist/strategies/router.js +70 -0
- package/dist/strategies/router.js.map +1 -0
- package/dist/strategies/sequential.d.ts +12 -0
- package/dist/strategies/sequential.d.ts.map +1 -0
- package/dist/strategies/sequential.js +39 -0
- package/dist/strategies/sequential.js.map +1 -0
- package/dist/strategies/types.d.ts +62 -0
- package/dist/strategies/types.d.ts.map +1 -0
- package/dist/strategies/types.js +5 -0
- package/dist/strategies/types.js.map +1 -0
- package/dist/types.d.ts +83 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +28 -0
- package/src/agent.ts +243 -0
- package/src/cli-utils.ts +73 -0
- package/src/errors.ts +146 -0
- package/src/index.ts +124 -0
- package/src/llm-provider.ts +88 -0
- package/src/logger.ts +97 -0
- package/src/mock-llm.ts +433 -0
- package/src/orchestrator-helpers.ts +40 -0
- package/src/orchestrator.ts +522 -0
- package/src/pipeline-registry.ts +253 -0
- package/src/pipeline.ts +265 -0
- package/src/skill.ts +127 -0
- package/src/skills/io-skill.ts +133 -0
- package/src/skills/llm-skill.ts +207 -0
- package/src/skills/transform-skill.ts +61 -0
- package/src/state/file-store.ts +119 -0
- package/src/state/memory-store.ts +82 -0
- package/src/state/types.ts +53 -0
- package/src/strategies/custom.ts +24 -0
- package/src/strategies/plan-and-execute.ts +268 -0
- package/src/strategies/react.ts +239 -0
- package/src/strategies/router.ts +101 -0
- package/src/strategies/sequential.ts +55 -0
- package/src/strategies/types.ts +97 -0
- package/src/types.ts +102 -0
- package/tsconfig.json +9 -0
package/src/logger.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured logger with level support and pluggable transports.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';
|
|
6
|
+
|
|
7
|
+
export interface LogEntry {
|
|
8
|
+
level: LogLevel;
|
|
9
|
+
message: string;
|
|
10
|
+
timestamp: string;
|
|
11
|
+
context?: string;
|
|
12
|
+
data?: unknown;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type LogTransport = (entry: LogEntry) => void;
|
|
16
|
+
|
|
17
|
+
const LEVELS: Record<LogLevel, number> = {
|
|
18
|
+
debug: 0,
|
|
19
|
+
info: 1,
|
|
20
|
+
warn: 2,
|
|
21
|
+
error: 3,
|
|
22
|
+
silent: 99,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const COLORS: Record<LogLevel, string> = {
|
|
26
|
+
debug: '\x1b[36m', // cyan
|
|
27
|
+
info: '\x1b[32m', // green
|
|
28
|
+
warn: '\x1b[33m', // yellow
|
|
29
|
+
error: '\x1b[31m', // red
|
|
30
|
+
silent: '',
|
|
31
|
+
};
|
|
32
|
+
const RESET = '\x1b[0m';
|
|
33
|
+
|
|
34
|
+
function consoleTransport(entry: LogEntry): void {
|
|
35
|
+
if (entry.level === 'silent') return;
|
|
36
|
+
const color = COLORS[entry.level] ?? '';
|
|
37
|
+
const prefix = `${color}[${entry.level.toUpperCase()}]${RESET}`;
|
|
38
|
+
const ctx = entry.context ? ` \x1b[90m(${entry.context})\x1b[0m` : '';
|
|
39
|
+
const msg = `${entry.timestamp} ${prefix}${ctx} ${entry.message}`;
|
|
40
|
+
if (entry.data !== undefined) {
|
|
41
|
+
const method = entry.level === 'error' ? 'error' : 'log';
|
|
42
|
+
console[method](msg, entry.data);
|
|
43
|
+
} else {
|
|
44
|
+
const method = entry.level === 'error' ? 'error' : 'log';
|
|
45
|
+
console[method](msg);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class Logger {
|
|
50
|
+
private level: LogLevel;
|
|
51
|
+
private context: string | undefined;
|
|
52
|
+
private transports: LogTransport[];
|
|
53
|
+
|
|
54
|
+
constructor(options: {
|
|
55
|
+
level?: LogLevel;
|
|
56
|
+
context?: string;
|
|
57
|
+
transports?: LogTransport[];
|
|
58
|
+
} = {}) {
|
|
59
|
+
this.level = options.level ?? 'info';
|
|
60
|
+
this.context = options.context;
|
|
61
|
+
this.transports = options.transports ?? [consoleTransport];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
child(context: string): Logger {
|
|
65
|
+
return new Logger({
|
|
66
|
+
level: this.level,
|
|
67
|
+
context: this.context ? `${this.context}:${context}` : context,
|
|
68
|
+
transports: this.transports,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
setLevel(level: LogLevel): void {
|
|
73
|
+
this.level = level;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private write(level: LogLevel, message: string, data?: unknown): void {
|
|
77
|
+
if (LEVELS[level] < LEVELS[this.level]) return;
|
|
78
|
+
const entry: LogEntry = {
|
|
79
|
+
level,
|
|
80
|
+
message,
|
|
81
|
+
timestamp: new Date().toISOString(),
|
|
82
|
+
context: this.context,
|
|
83
|
+
data,
|
|
84
|
+
};
|
|
85
|
+
for (const transport of this.transports) {
|
|
86
|
+
transport(entry);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
debug(message: string, data?: unknown): void { this.write('debug', message, data); }
|
|
91
|
+
info(message: string, data?: unknown): void { this.write('info', message, data); }
|
|
92
|
+
warn(message: string, data?: unknown): void { this.write('warn', message, data); }
|
|
93
|
+
error(message: string, data?: unknown): void { this.write('error', message, data); }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Default root logger. */
|
|
97
|
+
export const logger = new Logger({ level: (process.env['LOG_LEVEL'] as LogLevel) ?? 'info' });
|
package/src/mock-llm.ts
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MockLLMProvider — a deterministic LLM provider for testing.
|
|
3
|
+
*
|
|
4
|
+
* Matches incoming prompts against registered patterns and returns
|
|
5
|
+
* pre-canned JSON responses. Falls back to a default response if no
|
|
6
|
+
* pattern matches.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* const mock = new MockLLMProvider([
|
|
10
|
+
* { match: /summarize/, response: '{"summary":"test"}' },
|
|
11
|
+
* { match: /classify/, response: '{"sentiment":"positive"}' },
|
|
12
|
+
* ]);
|
|
13
|
+
* const orchestrator = new Orchestrator({ llm: { default: mock } });
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { LLMProvider, LLMRegistry } from './llm-provider.js';
|
|
17
|
+
import type { Message, LLMOptions, LLMResponse, LLMChunk } from './types.js';
|
|
18
|
+
import { Orchestrator } from './orchestrator.js';
|
|
19
|
+
import { MemoryStore } from './state/memory-store.js';
|
|
20
|
+
|
|
21
|
+
export interface MockResponse {
|
|
22
|
+
/** Regex or string to match against the last user message content. */
|
|
23
|
+
match: RegExp | string;
|
|
24
|
+
/** The raw text response to return. */
|
|
25
|
+
response: string;
|
|
26
|
+
/** Optional: only match this many times (default: unlimited). */
|
|
27
|
+
times?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class MockLLMProvider implements LLMProvider {
|
|
31
|
+
readonly name = 'mock';
|
|
32
|
+
readonly model = 'mock-model';
|
|
33
|
+
|
|
34
|
+
private callCount = 0;
|
|
35
|
+
private responseCounts = new Map<number, number>();
|
|
36
|
+
|
|
37
|
+
constructor(
|
|
38
|
+
private readonly responses: MockResponse[],
|
|
39
|
+
private readonly defaultResponse: string = '{"result":"mock-response","ok":true}',
|
|
40
|
+
private readonly delayMs: number = 0,
|
|
41
|
+
) {}
|
|
42
|
+
|
|
43
|
+
async chat(messages: Message[], _options?: LLMOptions): Promise<LLMResponse> {
|
|
44
|
+
if (this.delayMs > 0) {
|
|
45
|
+
await new Promise((r) => setTimeout(r, this.delayMs));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.callCount++;
|
|
49
|
+
|
|
50
|
+
// Find the last user message
|
|
51
|
+
const lastUser = [...messages].reverse().find((m) => m.role === 'user');
|
|
52
|
+
const content = lastUser?.content ?? '';
|
|
53
|
+
|
|
54
|
+
// Find matching response
|
|
55
|
+
let responseText = this.defaultResponse;
|
|
56
|
+
for (let i = 0; i < this.responses.length; i++) {
|
|
57
|
+
const r = this.responses[i]!;
|
|
58
|
+
const matches =
|
|
59
|
+
typeof r.match === 'string'
|
|
60
|
+
? content.toLowerCase().includes(r.match.toLowerCase())
|
|
61
|
+
: r.match.test(content);
|
|
62
|
+
|
|
63
|
+
if (matches) {
|
|
64
|
+
const usedTimes = this.responseCounts.get(i) ?? 0;
|
|
65
|
+
if (r.times === undefined || usedTimes < r.times) {
|
|
66
|
+
responseText = r.response;
|
|
67
|
+
this.responseCounts.set(i, usedTimes + 1);
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
content: responseText,
|
|
75
|
+
model: this.model,
|
|
76
|
+
usage: {
|
|
77
|
+
inputTokens: Math.ceil(content.length / 4),
|
|
78
|
+
outputTokens: Math.ceil(responseText.length / 4),
|
|
79
|
+
totalTokens: Math.ceil((content.length + responseText.length) / 4),
|
|
80
|
+
},
|
|
81
|
+
stopReason: 'stop',
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async *stream(messages: Message[], options?: LLMOptions): AsyncIterable<LLMChunk> {
|
|
86
|
+
const response = await this.chat(messages, options);
|
|
87
|
+
yield {
|
|
88
|
+
content: response.content,
|
|
89
|
+
done: true,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
get totalCalls(): number {
|
|
94
|
+
return this.callCount;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
reset(): void {
|
|
98
|
+
this.callCount = 0;
|
|
99
|
+
this.responseCounts.clear();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Create a MockLLMProvider that returns valid JSON for any skill type.
|
|
105
|
+
* Pre-configured with sensible responses for all flomatai example skills.
|
|
106
|
+
*/
|
|
107
|
+
export function createTestLLM(overrides: MockResponse[] = []): MockLLMProvider {
|
|
108
|
+
return new MockLLMProvider([
|
|
109
|
+
...overrides,
|
|
110
|
+
|
|
111
|
+
// ── High-priority patterns (matched first to prevent false positives) ──────
|
|
112
|
+
|
|
113
|
+
// ETL executive narrative — must come before outage pattern (prompt contains "Incidents:")
|
|
114
|
+
{
|
|
115
|
+
match: /Write an executive business report|executive.*business.*report/i,
|
|
116
|
+
response: JSON.stringify({
|
|
117
|
+
executiveSummary: 'Q1 2024 showed strong revenue growth with total sales of $67,497.29 across all regions. User engagement remains high with 87% active rate.',
|
|
118
|
+
salesHighlights: 'Widget Pro and Gadget Plus drove 68% of total revenue. North region led all regions at $18,432.',
|
|
119
|
+
userHighlights: '15 total users with 87% active rate. Enterprise tier generating highest lifetime value at $20,610 average.',
|
|
120
|
+
systemHighlights: 'System maintained 99.97% uptime with only 1 incident. P99 latency at 890ms needs monitoring.',
|
|
121
|
+
recommendations: ['Increase Gadget Plus inventory for West region', 'Implement churn prevention for inactive basic tier users', 'Optimize API P99 latency below 500ms'],
|
|
122
|
+
riskFlags: ['P99 latency at 890ms approaching SLA limit'],
|
|
123
|
+
}),
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
// Classify mention — must come before outage pattern (mention excerpts can contain incident-related terms)
|
|
127
|
+
{
|
|
128
|
+
match: /^Classify this mention of/i,
|
|
129
|
+
response: JSON.stringify({
|
|
130
|
+
id: 'mock-001',
|
|
131
|
+
sentiment: 'positive',
|
|
132
|
+
relevance: 0.9,
|
|
133
|
+
category: 'praise',
|
|
134
|
+
urgency: 'low',
|
|
135
|
+
requiresResponse: false,
|
|
136
|
+
summary: 'User praised the product features and ease of use.',
|
|
137
|
+
keyIssues: [],
|
|
138
|
+
}),
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
// Classify mention - negative/urgent (outage content in classify prompts)
|
|
142
|
+
{
|
|
143
|
+
match: /outage|frustrated|503|incident/i,
|
|
144
|
+
response: JSON.stringify({
|
|
145
|
+
id: 'mock-005',
|
|
146
|
+
sentiment: 'negative',
|
|
147
|
+
relevance: 1.0,
|
|
148
|
+
category: 'bug-report',
|
|
149
|
+
urgency: 'critical',
|
|
150
|
+
requiresResponse: true,
|
|
151
|
+
summary: 'User reporting API outage with 503 errors.',
|
|
152
|
+
keyIssues: ['API unavailable', '503 errors', 'Status page inaccurate'],
|
|
153
|
+
}),
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
// Research / brief
|
|
157
|
+
{
|
|
158
|
+
match: /content brief|research.*topic|topic.*brief/i,
|
|
159
|
+
response: JSON.stringify({
|
|
160
|
+
topic: 'Test Topic',
|
|
161
|
+
summary: 'A comprehensive overview of the test topic for demonstration purposes.',
|
|
162
|
+
keyPoints: ['Key point 1', 'Key point 2', 'Key point 3', 'Key point 4', 'Key point 5'],
|
|
163
|
+
audience: 'Technical professionals',
|
|
164
|
+
tone: 'professional',
|
|
165
|
+
keywords: ['keyword1', 'keyword2', 'keyword3'],
|
|
166
|
+
uniqueAngle: 'Practical implementation focus',
|
|
167
|
+
}),
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
// Blog format
|
|
171
|
+
{
|
|
172
|
+
match: /blog post|write.*blog/i,
|
|
173
|
+
response: JSON.stringify({
|
|
174
|
+
title: 'The Complete Guide to Test Topic',
|
|
175
|
+
slug: 'complete-guide-test-topic',
|
|
176
|
+
metaDescription: 'A comprehensive look at test topic with practical examples.',
|
|
177
|
+
content: '## Introduction\n\nTest blog content here.\n\n## Key Points\n\n- Point 1\n- Point 2\n\n## Conclusion\n\nIn summary, test topic is important.',
|
|
178
|
+
readingTimeMinutes: 3,
|
|
179
|
+
}),
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
// Twitter thread
|
|
183
|
+
{
|
|
184
|
+
match: /twitter thread|tweet/i,
|
|
185
|
+
response: JSON.stringify({
|
|
186
|
+
tweets: [
|
|
187
|
+
{ position: 1, text: '🧵 Test tweet 1 about this topic (thread)', charCount: 50 },
|
|
188
|
+
{ position: 2, text: 'Tweet 2 with more details about the subject matter', charCount: 52 },
|
|
189
|
+
{ position: 3, text: 'Tweet 3: Key insight here that readers will find valuable', charCount: 58 },
|
|
190
|
+
],
|
|
191
|
+
hashtags: ['#TestTopic', '#Learning'],
|
|
192
|
+
}),
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
// LinkedIn
|
|
196
|
+
{
|
|
197
|
+
match: /linkedin post/i,
|
|
198
|
+
response: JSON.stringify({
|
|
199
|
+
hook: 'This changes everything about how we work.',
|
|
200
|
+
body: 'I recently learned something that transformed my approach.\n\nHere is what I discovered...\n\nThe key insight is practical application.',
|
|
201
|
+
callToAction: 'What has been your experience? Share below.',
|
|
202
|
+
hashtags: ['#Professional', '#Growth'],
|
|
203
|
+
fullPost: 'This changes everything about how we work.\n\nI recently learned something that transformed my approach.\n\nWhat has been your experience? Share below.\n\n#Professional #Growth',
|
|
204
|
+
}),
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
// TL;DR
|
|
208
|
+
{
|
|
209
|
+
match: /tl.?dr|newsletter snippet/i,
|
|
210
|
+
response: JSON.stringify({
|
|
211
|
+
headline: 'Key Insight on Test Topic',
|
|
212
|
+
summary: 'Two sentence summary of the most important points from this topic.',
|
|
213
|
+
bullets: ['First key takeaway here', 'Second important point', 'Third actionable item'],
|
|
214
|
+
takeaway: 'The one thing to remember about this topic',
|
|
215
|
+
}),
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
// Translation
|
|
219
|
+
{
|
|
220
|
+
match: /translate.*to|translat/i,
|
|
221
|
+
response: '# Contenido traducido\n\nEste es el contenido traducido al español.',
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
// RAG answer
|
|
225
|
+
{
|
|
226
|
+
match: /answer.*question|question.*answer|q&a|provided context/i,
|
|
227
|
+
response: JSON.stringify({
|
|
228
|
+
answer: 'Based on the provided documents, the answer is that TypeScript provides strong type inference which automatically determines variable types from their initial values.',
|
|
229
|
+
citations: [
|
|
230
|
+
{ source: 'typescript-handbook.md', excerpt: 'TypeScript infers types automatically when you declare a variable with an initial value.' },
|
|
231
|
+
],
|
|
232
|
+
confidence: 'high',
|
|
233
|
+
}),
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
// Code review
|
|
237
|
+
{
|
|
238
|
+
match: /review.*file|code.*review|diff/i,
|
|
239
|
+
response: JSON.stringify({
|
|
240
|
+
filename: 'test.ts',
|
|
241
|
+
severity: 'info',
|
|
242
|
+
summary: 'The code looks well-structured with proper TypeScript typing. No critical issues found.',
|
|
243
|
+
comments: [
|
|
244
|
+
{ line: 15, body: 'Consider adding error boundary handling here', severity: 'info' },
|
|
245
|
+
],
|
|
246
|
+
approved: true,
|
|
247
|
+
}),
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
// Translation plan (code translation)
|
|
251
|
+
{
|
|
252
|
+
match: /translation plan|migrate.*from|translate.*python/i,
|
|
253
|
+
response: JSON.stringify({
|
|
254
|
+
sourceLanguage: 'Python',
|
|
255
|
+
targetLanguage: 'TypeScript',
|
|
256
|
+
files: [
|
|
257
|
+
{ sourceFile: 'src/auth.py', targetFile: 'src/auth.ts', priority: 1, notes: 'Convert PBKDF2 to Node.js crypto. Replace Python dataclasses with interfaces.' },
|
|
258
|
+
{ sourceFile: 'src/cache.py', targetFile: 'src/cache.ts', priority: 2, notes: 'Convert threading.Lock to async mutex. OrderedDict becomes Map.' },
|
|
259
|
+
{ sourceFile: 'src/api_client.py', targetFile: 'src/api_client.ts', priority: 3, notes: 'Replace urllib with fetch API. Convert to async/await.' },
|
|
260
|
+
],
|
|
261
|
+
generalNotes: 'Use ES2022 features. Add explicit return types. Use Map instead of dict.',
|
|
262
|
+
estimatedComplexity: 'medium',
|
|
263
|
+
}),
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
// Translate file (code translation)
|
|
267
|
+
{
|
|
268
|
+
match: /translate.*source|source.*code|typescript.*equivalent|python.*to/i,
|
|
269
|
+
response: JSON.stringify({
|
|
270
|
+
sourceFile: 'src/auth.py',
|
|
271
|
+
targetFile: 'src/auth.ts',
|
|
272
|
+
translatedCode: '// Translated TypeScript file\nimport crypto from "crypto";\n\nexport class AuthService {\n constructor(private secret: string) {}\n\n hashPassword(password: string, salt: string): string {\n return crypto.pbkdf2Sync(password, salt, 100000, 32, "sha256").toString("base64");\n }\n}\n',
|
|
273
|
+
linesOfCode: 12,
|
|
274
|
+
notes: 'Converted Python PBKDF2 to Node.js crypto.pbkdf2Sync',
|
|
275
|
+
warnings: [],
|
|
276
|
+
}),
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
// Daily digest — must come BEFORE draft-response to avoid prefix matching on "DRAFTED RESPONSES"
|
|
280
|
+
{
|
|
281
|
+
match: /daily.*digest|monitoring.*digest|brand.*digest|social.*report|Generate a daily social/i,
|
|
282
|
+
response: JSON.stringify({
|
|
283
|
+
date: new Date().toISOString().split('T')[0],
|
|
284
|
+
brand: 'Acme Corp',
|
|
285
|
+
totalMentions: 8,
|
|
286
|
+
sentimentBreakdown: { positive: 4, negative: 2, neutral: 1, mixed: 1 },
|
|
287
|
+
urgentItems: [
|
|
288
|
+
{ mentionId: 'mock-005', title: 'API outage report', urgency: 'critical', summary: 'User reporting 503 errors for 30 minutes', draftResponse: 'We are aware of the issue and working to resolve it.' },
|
|
289
|
+
],
|
|
290
|
+
executiveSummary: 'Acme Corp had a generally positive week with 4 positive mentions and 2 negative. The API outage generated critical mentions requiring immediate response.',
|
|
291
|
+
topThemes: ['Product quality', 'API reliability', 'Pricing concerns'],
|
|
292
|
+
recommendations: ['Address API outage publicly', 'Create SLA transparency page', 'Follow up with pricing FAQ'],
|
|
293
|
+
}),
|
|
294
|
+
},
|
|
295
|
+
|
|
296
|
+
// Draft response (individual mention)
|
|
297
|
+
{
|
|
298
|
+
match: /draft.*response|response.*mention|respond.*to/i,
|
|
299
|
+
response: JSON.stringify({
|
|
300
|
+
mentionId: 'mock-001',
|
|
301
|
+
draft: 'Thank you for your feedback! We appreciate you taking the time to share your experience.',
|
|
302
|
+
tone: 'professional',
|
|
303
|
+
channel: 'hacker-news',
|
|
304
|
+
charCount: 89,
|
|
305
|
+
requiresHumanReview: false,
|
|
306
|
+
}),
|
|
307
|
+
},
|
|
308
|
+
|
|
309
|
+
// Research extract facts
|
|
310
|
+
{
|
|
311
|
+
match: /extract.*facts|facts.*extract|relevant.*facts/i,
|
|
312
|
+
response: JSON.stringify({
|
|
313
|
+
facts: [
|
|
314
|
+
'Large language models have shown significant improvements in code generation tasks',
|
|
315
|
+
'GitHub Copilot reported 55% of code suggestions accepted by developers in 2024',
|
|
316
|
+
'AI coding tools reduce debugging time by an average of 30-40%',
|
|
317
|
+
],
|
|
318
|
+
sourceUrl: 'https://example.com/article',
|
|
319
|
+
relevanceScore: 0.85,
|
|
320
|
+
}),
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
// Research synthesize
|
|
324
|
+
{
|
|
325
|
+
match: /synthesize.*report|research.*report|comprehensive.*report/i,
|
|
326
|
+
response: JSON.stringify({
|
|
327
|
+
title: 'Research Report: Test Topic',
|
|
328
|
+
summary: 'This report examines the current state and future trajectory of the researched topic based on multiple sources gathered from across the web.',
|
|
329
|
+
findings: [
|
|
330
|
+
{ section: 'Current State', content: 'The field is rapidly evolving with significant developments in recent years.' },
|
|
331
|
+
{ section: 'Key Trends', content: 'Three major trends are emerging that will shape the direction of this field.' },
|
|
332
|
+
{ section: 'Future Outlook', content: 'Experts predict continued growth and maturation over the next 2-3 years.' },
|
|
333
|
+
],
|
|
334
|
+
sourcesUsed: ['https://example.com/1', 'https://example.com/2'],
|
|
335
|
+
confidence: 'medium',
|
|
336
|
+
gaps: ['More longitudinal data needed', 'Industry-specific studies lacking'],
|
|
337
|
+
}),
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
// ReAct thought - general finish
|
|
341
|
+
{
|
|
342
|
+
match: /what do you do next|step.*reasoning/i,
|
|
343
|
+
response: JSON.stringify({
|
|
344
|
+
thought: 'I have gathered sufficient information through my searches. I now have 15 relevant facts from 3 sources. I will synthesize these into a comprehensive report.',
|
|
345
|
+
action: {
|
|
346
|
+
type: 'use_skill',
|
|
347
|
+
skill: 'synthesize-report',
|
|
348
|
+
input: {
|
|
349
|
+
topic: 'test topic',
|
|
350
|
+
allFacts: [
|
|
351
|
+
{ facts: ['Fact 1', 'Fact 2', 'Fact 3'], sourceUrl: 'https://example.com', relevanceScore: 0.85 },
|
|
352
|
+
],
|
|
353
|
+
searchesPerformed: ['test topic overview', 'test topic trends'],
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
}),
|
|
357
|
+
},
|
|
358
|
+
|
|
359
|
+
// Verlivo research docs
|
|
360
|
+
{
|
|
361
|
+
match: /microservice|verlivo|architecture.*service|extract.*service/i,
|
|
362
|
+
response: JSON.stringify({
|
|
363
|
+
project_summary: 'A multi-tenant SaaS platform with 3 core microservices.',
|
|
364
|
+
architecture_context: 'Services communicate via REST APIs with JWT authentication.',
|
|
365
|
+
services: [
|
|
366
|
+
{ name: 'auth-service', port: 3001, phase: 1, team: 'Backend', database: 'PostgreSQL', description: 'Authentication service', external_integrations: ['SendGrid'], depends_on: [], high_volume: false, has_workers: false, has_clickhouse: false },
|
|
367
|
+
],
|
|
368
|
+
}),
|
|
369
|
+
},
|
|
370
|
+
|
|
371
|
+
// Verlivo planning steps
|
|
372
|
+
{
|
|
373
|
+
match: /definition|use.?case|feature|integration|event|schema|openapi/i,
|
|
374
|
+
response: JSON.stringify({
|
|
375
|
+
service_name: 'auth-service',
|
|
376
|
+
phase: 1,
|
|
377
|
+
team: 'Backend',
|
|
378
|
+
complete_plan: '# auth-service Plan\n\n## Definition\nHandles authentication and JWT issuance.\n\n## Use Cases\n- User login\n- Token refresh\n\n## Features\n- JWT generation\n- RBAC enforcement',
|
|
379
|
+
}),
|
|
380
|
+
},
|
|
381
|
+
]);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Create a minimal MockLLMRegistry for use in orchestrators.
|
|
386
|
+
*/
|
|
387
|
+
export function createMockLLMRegistry(overrides: MockResponse[] = []): Record<string, LLMProvider> {
|
|
388
|
+
const mock = createTestLLM(overrides);
|
|
389
|
+
return {
|
|
390
|
+
default: mock,
|
|
391
|
+
research: mock,
|
|
392
|
+
planning: mock,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Create a ready-to-use Orchestrator wired with MockLLMProvider + MemoryStore
|
|
398
|
+
* and the standard console step-logging hooks.
|
|
399
|
+
*
|
|
400
|
+
* This eliminates the ~10-line boilerplate repeated in every example test.ts:
|
|
401
|
+
*
|
|
402
|
+
* // Before:
|
|
403
|
+
* const orchestrator = new Orchestrator({
|
|
404
|
+
* llm: { default: createTestLLM(), planning: createTestLLM() },
|
|
405
|
+
* state: new MemoryStore(),
|
|
406
|
+
* hooks: { beforeStep: ..., afterStep: ..., onError: ... },
|
|
407
|
+
* });
|
|
408
|
+
*
|
|
409
|
+
* // After:
|
|
410
|
+
* const orchestrator = createTestOrchestrator(['planning']);
|
|
411
|
+
*
|
|
412
|
+
* @param extraLLMKeys Additional LLM registry keys to populate with the same mock
|
|
413
|
+
* (beyond the always-present 'default'). E.g. ['planning', 'research'].
|
|
414
|
+
* @param overrides Optional MockResponse patterns forwarded to createTestLLM().
|
|
415
|
+
*/
|
|
416
|
+
export function createTestOrchestrator(
|
|
417
|
+
extraLLMKeys: string[] = [],
|
|
418
|
+
overrides: MockResponse[] = [],
|
|
419
|
+
): Orchestrator {
|
|
420
|
+
const mock = createTestLLM(overrides);
|
|
421
|
+
const llm: LLMRegistry = { default: mock };
|
|
422
|
+
for (const key of extraLLMKeys) llm[key] = mock;
|
|
423
|
+
|
|
424
|
+
return new Orchestrator({
|
|
425
|
+
llm,
|
|
426
|
+
state: new MemoryStore(),
|
|
427
|
+
hooks: {
|
|
428
|
+
beforeStep: (step) => { process.stdout.write(` → ${step.name} ... `); },
|
|
429
|
+
afterStep: (_step, record) => { console.log(`done (${record.durationMs}ms)`); },
|
|
430
|
+
onError: (step, err) => { console.error(`\n ✗ ${step?.name}: ${err.message}`); },
|
|
431
|
+
},
|
|
432
|
+
});
|
|
433
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* orchestrator-helpers — convenience factories for common Orchestrator configuration.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from the repeated boilerplate found in every example's orchestrator.ts.
|
|
5
|
+
* Import these instead of writing the hooks object by hand.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { OrchestratorHooks } from './orchestrator.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Returns the standard console-logging hooks used by all flomatai examples.
|
|
12
|
+
*
|
|
13
|
+
* Output format:
|
|
14
|
+
* ▶ <label> [<runId>]
|
|
15
|
+
* → step-name ... done (42ms)
|
|
16
|
+
* ✓ completed in 1234ms (567 tokens)
|
|
17
|
+
*
|
|
18
|
+
* @param label Short description shown in the beforePipeline line (e.g. 'verlivo-planning').
|
|
19
|
+
* Defaults to 'Pipeline'.
|
|
20
|
+
*/
|
|
21
|
+
export function createConsoleHooks(label?: string): OrchestratorHooks {
|
|
22
|
+
return {
|
|
23
|
+
beforePipeline: (_pipeline, _input, runId) => {
|
|
24
|
+
console.log(`\n▶ ${label ?? 'Pipeline'} [${runId}]`);
|
|
25
|
+
},
|
|
26
|
+
afterPipeline: (_pipeline, run) => {
|
|
27
|
+
const icon = run.status === 'completed' ? '✓' : '✗';
|
|
28
|
+
console.log(`\n${icon} ${run.status} in ${run.durationMs}ms (${run.tokensUsed} tokens)`);
|
|
29
|
+
},
|
|
30
|
+
beforeStep: (step) => {
|
|
31
|
+
process.stdout.write(` → ${step.name} ... `);
|
|
32
|
+
},
|
|
33
|
+
afterStep: (_step, record) => {
|
|
34
|
+
console.log(`done (${record.durationMs}ms)`);
|
|
35
|
+
},
|
|
36
|
+
onError: (step, error) => {
|
|
37
|
+
console.error(`\n ✗ ${step?.name ?? 'pipeline'}: ${error.message}`);
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|