@memgrafter/flatagents 0.8.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/index.js ADDED
@@ -0,0 +1,1700 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ CheckpointManager: () => CheckpointManager,
34
+ CompositeHooks: () => CompositeHooks,
35
+ DefaultExecution: () => DefaultExecution,
36
+ FlatAgent: () => FlatAgent,
37
+ FlatMachine: () => FlatMachine,
38
+ LocalFileBackend: () => LocalFileBackend,
39
+ LocalFileLock: () => LocalFileLock,
40
+ MCPToolProvider: () => MCPToolProvider,
41
+ MDAPVotingExecution: () => MDAPVotingExecution,
42
+ MemoryBackend: () => MemoryBackend,
43
+ MockLLMBackend: () => MockLLMBackend,
44
+ NoOpLock: () => NoOpLock,
45
+ ParallelExecution: () => ParallelExecution,
46
+ ProfileManager: () => ProfileManager,
47
+ RetryExecution: () => RetryExecution,
48
+ VercelAIBackend: () => VercelAIBackend,
49
+ WebhookHooks: () => WebhookHooks,
50
+ evaluate: () => evaluate,
51
+ getExecutionType: () => getExecutionType,
52
+ inMemoryResultBackend: () => inMemoryResultBackend,
53
+ resolveModelConfig: () => resolveModelConfig,
54
+ setTemplateAllowlist: () => setTemplateAllowlist
55
+ });
56
+ module.exports = __toCommonJS(index_exports);
57
+
58
+ // src/flatagent.ts
59
+ var yaml2 = __toESM(require("yaml"));
60
+ var import_fs2 = require("fs");
61
+ var import_path2 = require("path");
62
+
63
+ // src/mcp.ts
64
+ var import_client = require("@modelcontextprotocol/sdk/client/index.js");
65
+ var import_stdio = require("@modelcontextprotocol/sdk/client/stdio.js");
66
+ var MCPToolProvider = class {
67
+ constructor() {
68
+ this.clients = /* @__PURE__ */ new Map();
69
+ }
70
+ async connect(servers) {
71
+ if (!servers || typeof servers !== "object") {
72
+ return;
73
+ }
74
+ for (const [name, server] of Object.entries(servers)) {
75
+ if (!name || !server || typeof server !== "object") {
76
+ continue;
77
+ }
78
+ const command = typeof server.command === "string" ? server.command.trim() : "";
79
+ if (!command) {
80
+ continue;
81
+ }
82
+ try {
83
+ const transport = new import_stdio.StdioClientTransport({
84
+ command,
85
+ args: server.args || [],
86
+ env: server.env
87
+ });
88
+ const client = new import_client.Client({ name, version: "1.0.0" });
89
+ await client.connect(transport);
90
+ this.clients.set(name, client);
91
+ } catch {
92
+ }
93
+ }
94
+ }
95
+ async listTools(filter) {
96
+ const tools = [];
97
+ for (const [serverName, client] of this.clients) {
98
+ const { tools: serverTools } = await client.listTools();
99
+ for (const tool of serverTools) {
100
+ const name = `${serverName}:${tool.name}`;
101
+ if (this.matchesFilter(name, filter)) tools.push({ ...tool, name });
102
+ }
103
+ }
104
+ return tools;
105
+ }
106
+ async callTool(name, args) {
107
+ const [server, tool] = name.split(":");
108
+ const client = this.clients.get(server);
109
+ if (!client) {
110
+ throw new Error(`MCP server '${server}' not found`);
111
+ }
112
+ return client.callTool({ name: tool, arguments: args });
113
+ }
114
+ matchesFilter(name, filter) {
115
+ if (filter?.deny?.some((p) => this.match(name, p))) return false;
116
+ if (filter?.allow && !filter.allow.some((p) => this.match(name, p))) return false;
117
+ return true;
118
+ }
119
+ match(name, pattern) {
120
+ if (pattern.includes("*")) {
121
+ return new RegExp(pattern.replace(/\*/g, ".*")).test(name);
122
+ }
123
+ return name === pattern;
124
+ }
125
+ };
126
+
127
+ // src/llm/vercel.ts
128
+ var import_ai = require("ai");
129
+ var import_openai = require("@ai-sdk/openai");
130
+ var import_anthropic = require("@ai-sdk/anthropic");
131
+ var import_cerebras = require("@ai-sdk/cerebras");
132
+ var import_openai_compatible = require("@ai-sdk/openai-compatible");
133
+ var PROVIDER_BASE_URLS = {
134
+ cerebras: "https://api.cerebras.ai/v1",
135
+ groq: "https://api.groq.com/openai/v1",
136
+ together: "https://api.together.xyz/v1",
137
+ fireworks: "https://api.fireworks.ai/inference/v1",
138
+ deepseek: "https://api.deepseek.com/v1",
139
+ mistral: "https://api.mistral.ai/v1",
140
+ perplexity: "https://api.perplexity.ai"
141
+ };
142
+ var VercelAIBackend = class {
143
+ constructor(config) {
144
+ this.totalCost = 0;
145
+ this.totalApiCalls = 0;
146
+ this.model = this.createModel(config);
147
+ }
148
+ createModel(config) {
149
+ const { provider = "openai", name: modelName, apiKey, baseURL } = config;
150
+ const providerLower = provider.toLowerCase();
151
+ const providerUpper = provider.toUpperCase();
152
+ const resolvedApiKey = apiKey ?? process.env[`${providerUpper}_API_KEY`];
153
+ const resolvedBaseURL = baseURL ?? process.env[`${providerUpper}_BASE_URL`] ?? PROVIDER_BASE_URLS[providerLower];
154
+ if (providerLower === "openai") {
155
+ const openai = (0, import_openai.createOpenAI)({ apiKey: resolvedApiKey });
156
+ return openai.chat(modelName);
157
+ }
158
+ if (providerLower === "anthropic") {
159
+ const anthropic = (0, import_anthropic.createAnthropic)({ apiKey: resolvedApiKey });
160
+ return anthropic(modelName);
161
+ }
162
+ if (providerLower === "cerebras") {
163
+ const cerebras = (0, import_cerebras.createCerebras)({ apiKey: resolvedApiKey });
164
+ return cerebras(modelName);
165
+ }
166
+ if (!resolvedBaseURL) {
167
+ throw new Error(
168
+ `Unknown provider "${provider}". Set ${providerUpper}_BASE_URL environment variable.`
169
+ );
170
+ }
171
+ const compatible = (0, import_openai_compatible.createOpenAICompatible)({
172
+ name: providerLower,
173
+ baseURL: resolvedBaseURL,
174
+ headers: {
175
+ Authorization: `Bearer ${resolvedApiKey}`
176
+ }
177
+ });
178
+ return compatible(modelName);
179
+ }
180
+ async call(messages, options) {
181
+ const response = await this.callRaw(messages, options);
182
+ return response.text;
183
+ }
184
+ async callRaw(messages, options) {
185
+ this.totalApiCalls++;
186
+ const systemMessage = messages.find((m) => m.role === "system");
187
+ const userMessage = messages.find((m) => m.role === "user");
188
+ const generateParams = {
189
+ model: this.model,
190
+ system: systemMessage?.content,
191
+ prompt: userMessage?.content
192
+ };
193
+ if (options?.temperature !== void 0) generateParams.temperature = options.temperature;
194
+ if (options?.max_tokens !== void 0) generateParams.maxTokens = options.max_tokens;
195
+ if (options?.top_p !== void 0) generateParams.topP = options.top_p;
196
+ if (options?.frequency_penalty !== void 0) generateParams.frequencyPenalty = options.frequency_penalty;
197
+ if (options?.presence_penalty !== void 0) generateParams.presencePenalty = options.presence_penalty;
198
+ const response = await (0, import_ai.generateText)(generateParams);
199
+ return response;
200
+ }
201
+ };
202
+
203
+ // src/llm/mock.ts
204
+ var MockLLMBackend = class {
205
+ /**
206
+ * Create a mock backend with predefined responses.
207
+ *
208
+ * @param responses - Array of responses to return in order
209
+ * @param defaultResponse - Response to use when responses are exhausted
210
+ */
211
+ constructor(responses = [], defaultResponse = { content: '{"result": "mock"}' }) {
212
+ this.totalCost = 0;
213
+ this.totalApiCalls = 0;
214
+ this.responseIndex = 0;
215
+ this.responses = responses;
216
+ this.defaultResponse = defaultResponse;
217
+ }
218
+ async call(messages, options) {
219
+ this.totalApiCalls++;
220
+ if (this.responseIndex < this.responses.length) {
221
+ return this.responses[this.responseIndex++].content;
222
+ }
223
+ return this.defaultResponse.content;
224
+ }
225
+ async callRaw(messages, options) {
226
+ this.totalApiCalls++;
227
+ const response = this.responseIndex < this.responses.length ? this.responses[this.responseIndex++] : this.defaultResponse;
228
+ return response?.raw ?? { text: response?.content };
229
+ }
230
+ /**
231
+ * Reset the response index to start from the beginning.
232
+ */
233
+ reset() {
234
+ this.responseIndex = 0;
235
+ this.totalApiCalls = 0;
236
+ this.totalCost = 0;
237
+ }
238
+ /**
239
+ * Add responses to the queue.
240
+ */
241
+ addResponses(responses) {
242
+ this.responses.push(...responses);
243
+ }
244
+ };
245
+
246
+ // src/profiles.ts
247
+ var import_fs = require("fs");
248
+ var import_path = require("path");
249
+ var import_yaml = __toESM(require("yaml"));
250
+ var profileManagerCache = /* @__PURE__ */ new Map();
251
+ var ProfileManager = class _ProfileManager {
252
+ constructor(profilesFile) {
253
+ this.profiles = {};
254
+ this.profilesFile = profilesFile;
255
+ if (profilesFile) {
256
+ this.loadProfiles(profilesFile);
257
+ }
258
+ }
259
+ /**
260
+ * Get or create a ProfileManager for a directory.
261
+ * Caches instances by directory to avoid re-reading profiles.yml.
262
+ */
263
+ static getInstance(configDir) {
264
+ if (!profileManagerCache.has(configDir)) {
265
+ const profilesPath = (0, import_path.join)(configDir, "profiles.yml");
266
+ if ((0, import_fs.existsSync)(profilesPath)) {
267
+ profileManagerCache.set(configDir, new _ProfileManager(profilesPath));
268
+ } else {
269
+ profileManagerCache.set(configDir, new _ProfileManager());
270
+ }
271
+ }
272
+ return profileManagerCache.get(configDir);
273
+ }
274
+ /**
275
+ * Clear cached ProfileManager instances.
276
+ */
277
+ static clearCache() {
278
+ profileManagerCache.clear();
279
+ }
280
+ loadProfiles(profilesFile) {
281
+ if (!(0, import_fs.existsSync)(profilesFile)) {
282
+ return;
283
+ }
284
+ const content = (0, import_fs.readFileSync)(profilesFile, "utf-8");
285
+ const config = import_yaml.default.parse(content);
286
+ if ("spec" in config && config.spec !== "flatprofiles") {
287
+ throw new Error(
288
+ `Invalid profiles spec: expected 'flatprofiles', got '${config.spec}'`
289
+ );
290
+ }
291
+ const data = "data" in config ? config.data : config;
292
+ this.profiles = data.model_profiles ?? {};
293
+ this.defaultProfile = data.default;
294
+ this.overrideProfile = data.override;
295
+ }
296
+ /**
297
+ * Get a profile by name.
298
+ */
299
+ getProfile(name) {
300
+ return this.profiles[name];
301
+ }
302
+ /**
303
+ * Get all loaded profiles.
304
+ */
305
+ getProfiles() {
306
+ return this.profiles;
307
+ }
308
+ /**
309
+ * Get the default profile name.
310
+ */
311
+ getDefaultProfile() {
312
+ return this.defaultProfile;
313
+ }
314
+ /**
315
+ * Get the override profile name.
316
+ */
317
+ getOverrideProfile() {
318
+ return this.overrideProfile;
319
+ }
320
+ /**
321
+ * Resolve the final model configuration.
322
+ *
323
+ * Resolution order:
324
+ * 1. Start with default profile (if set)
325
+ * 2. Apply named profile (if agentModelConfig is string or has 'profile' key)
326
+ * 3. Merge inline overrides (if agentModelConfig is dict)
327
+ * 4. Apply override profile (trumps all)
328
+ *
329
+ * @param agentModelConfig - Agent's model config (string, object, or undefined)
330
+ * @returns Fully resolved model configuration
331
+ * @throws Error if a referenced profile is not found
332
+ */
333
+ resolveModelConfig(agentModelConfig) {
334
+ const result = {};
335
+ if (this.defaultProfile) {
336
+ const defaultCfg = this.getProfile(this.defaultProfile);
337
+ if (defaultCfg) {
338
+ Object.assign(result, defaultCfg);
339
+ } else {
340
+ console.warn(`Default profile '${this.defaultProfile}' not found`);
341
+ }
342
+ }
343
+ if (typeof agentModelConfig === "string") {
344
+ const profileCfg = this.getProfile(agentModelConfig);
345
+ if (profileCfg) {
346
+ Object.assign(result, profileCfg);
347
+ } else {
348
+ throw new Error(`Model profile '${agentModelConfig}' not found`);
349
+ }
350
+ } else if (typeof agentModelConfig === "object" && agentModelConfig) {
351
+ const profileName = agentModelConfig.profile;
352
+ if (profileName) {
353
+ const profileCfg = this.getProfile(profileName);
354
+ if (profileCfg) {
355
+ Object.assign(result, profileCfg);
356
+ } else {
357
+ throw new Error(`Model profile '${profileName}' not found`);
358
+ }
359
+ }
360
+ const { profile, ...inlineOverrides } = agentModelConfig;
361
+ for (const [key, value] of Object.entries(inlineOverrides)) {
362
+ if (value !== void 0 && value !== null) {
363
+ result[key] = value;
364
+ }
365
+ }
366
+ }
367
+ if (this.overrideProfile) {
368
+ const overrideCfg = this.getProfile(this.overrideProfile);
369
+ if (overrideCfg) {
370
+ Object.assign(result, overrideCfg);
371
+ } else {
372
+ console.warn(`Override profile '${this.overrideProfile}' not found`);
373
+ }
374
+ }
375
+ return result;
376
+ }
377
+ };
378
+ function resolveModelConfig(agentModelConfig, configDir, profilesFile) {
379
+ const manager = profilesFile ? new ProfileManager(profilesFile) : ProfileManager.getInstance(configDir);
380
+ return manager.resolveModelConfig(agentModelConfig);
381
+ }
382
+
383
+ // src/templating.ts
384
+ var nunjucks = __toESM(require("nunjucks"));
385
+
386
+ // src/template_allowlist.ts
387
+ var currentAllowlist = null;
388
+ var warned = /* @__PURE__ */ new Set();
389
+ function setTemplateAllowlist(allowlist) {
390
+ currentAllowlist = allowlist ?? null;
391
+ }
392
+ function warnOnce(key, message) {
393
+ if (warned.has(key)) return;
394
+ warned.add(key);
395
+ console.warn(message);
396
+ }
397
+ function warnTemplateAllowlist(template, source) {
398
+ if (!currentAllowlist) return;
399
+ const filterAllow = new Set(currentAllowlist.filters ?? []);
400
+ const tagAllow = new Set(currentAllowlist.tags ?? []);
401
+ const patterns = (currentAllowlist.patterns ?? []).map((pattern) => new RegExp(pattern, "g"));
402
+ for (const regex of patterns) {
403
+ if (regex.test(template)) {
404
+ warnOnce(
405
+ `${source}:pattern:${regex.source}`,
406
+ `[flatagents] template allowlist warning (${source}): pattern "${regex.source}" not allowed`
407
+ );
408
+ }
409
+ }
410
+ const tagMatches = template.matchAll(/{%\s*([A-Za-z_][\w-]*)/g);
411
+ for (const match of tagMatches) {
412
+ const tag = match[1];
413
+ if (tagAllow.size && !tagAllow.has(tag)) {
414
+ warnOnce(
415
+ `${source}:tag:${tag}`,
416
+ `[flatagents] template allowlist warning (${source}): tag "${tag}" not allowed`
417
+ );
418
+ }
419
+ }
420
+ const filterMatches = template.matchAll(/\|\s*([A-Za-z_][\w-]*)/g);
421
+ for (const match of filterMatches) {
422
+ const filter = match[1];
423
+ if (filterAllow.size && !filterAllow.has(filter)) {
424
+ warnOnce(
425
+ `${source}:filter:${filter}`,
426
+ `[flatagents] template allowlist warning (${source}): filter "${filter}" not allowed`
427
+ );
428
+ }
429
+ }
430
+ }
431
+
432
+ // src/templating.ts
433
+ var nunjucksEnv = new nunjucks.Environment(void 0, { autoescape: false });
434
+ nunjucksEnv.addFilter("tojson", (value) => JSON.stringify(value));
435
+ function renderTemplate(template, vars, source = "template") {
436
+ warnTemplateAllowlist(template, source);
437
+ return nunjucksEnv.renderString(template, vars);
438
+ }
439
+
440
+ // src/flatagent.ts
441
+ var FlatAgent = class {
442
+ /**
443
+ * Create a FlatAgent.
444
+ *
445
+ * @param configOrOptions - Config path, config object, or AgentOptions
446
+ */
447
+ constructor(configOrOptions) {
448
+ let configPath;
449
+ if (configOrOptions && typeof configOrOptions === "object" && "config" in configOrOptions && !("spec" in configOrOptions)) {
450
+ const options = configOrOptions;
451
+ if (typeof options.config === "string") {
452
+ configPath = options.config;
453
+ this.config = yaml2.parse((0, import_fs2.readFileSync)(options.config, "utf-8"));
454
+ } else {
455
+ this.config = options.config;
456
+ }
457
+ this.llmBackend = options.llmBackend;
458
+ this.configDir = options.configDir ?? (configPath ? (0, import_path2.dirname)(configPath) : process.cwd());
459
+ this.profilesFile = options.profilesFile;
460
+ } else if (typeof configOrOptions === "string") {
461
+ configPath = configOrOptions;
462
+ this.config = yaml2.parse((0, import_fs2.readFileSync)(configOrOptions, "utf-8"));
463
+ this.configDir = (0, import_path2.dirname)(configOrOptions);
464
+ } else {
465
+ this.config = configOrOptions;
466
+ this.configDir = process.cwd();
467
+ }
468
+ const configData = this.config && typeof this.config === "object" ? this.config.data : void 0;
469
+ if (configData?.expression_engine === "cel") {
470
+ throw new Error("expression_engine 'cel' is not supported in the JS SDK yet");
471
+ }
472
+ if (configData?.model) {
473
+ this.resolvedModelConfig = resolveModelConfig(
474
+ configData.model,
475
+ this.configDir,
476
+ this.profilesFile
477
+ );
478
+ } else {
479
+ this.resolvedModelConfig = { name: "" };
480
+ }
481
+ }
482
+ /**
483
+ * Get or create the LLM backend.
484
+ */
485
+ getBackend() {
486
+ if (!this.llmBackend) {
487
+ this.llmBackend = new VercelAIBackend({
488
+ provider: this.resolvedModelConfig.provider ?? "openai",
489
+ name: this.resolvedModelConfig.name,
490
+ baseURL: this.resolvedModelConfig.base_url
491
+ });
492
+ }
493
+ return this.llmBackend;
494
+ }
495
+ async call(input) {
496
+ if (this.config.data.mcp && !this.mcpProvider) {
497
+ this.mcpProvider = new MCPToolProvider();
498
+ await this.mcpProvider.connect(this.config.data.mcp.servers);
499
+ }
500
+ const tools = this.mcpProvider ? await this.mcpProvider.listTools(this.config.data.mcp?.tool_filter) : [];
501
+ const toolsPrompt = this.config.data.mcp?.tool_prompt ? renderTemplate(this.config.data.mcp.tool_prompt, { tools }, "flatagent.tool_prompt") : "";
502
+ const templateVars = { input, tools, tools_prompt: toolsPrompt, model: this.resolvedModelConfig };
503
+ const system = renderTemplate(this.config.data.system, templateVars, "flatagent.system");
504
+ let user = renderTemplate(this.config.data.user, templateVars, "flatagent.user");
505
+ if (this.config.data.instruction_suffix) {
506
+ user = `${user}
507
+
508
+ ${this.config.data.instruction_suffix}`;
509
+ }
510
+ const messages = [
511
+ { role: "system", content: system },
512
+ { role: "user", content: user }
513
+ ];
514
+ const backend = this.getBackend();
515
+ const text = await backend.call(messages, {
516
+ temperature: this.resolvedModelConfig.temperature,
517
+ max_tokens: this.resolvedModelConfig.max_tokens,
518
+ top_p: this.resolvedModelConfig.top_p,
519
+ top_k: this.resolvedModelConfig.top_k,
520
+ frequency_penalty: this.resolvedModelConfig.frequency_penalty,
521
+ presence_penalty: this.resolvedModelConfig.presence_penalty,
522
+ seed: this.resolvedModelConfig.seed
523
+ });
524
+ const output = this.extractOutput(text);
525
+ return { content: text, output };
526
+ }
527
+ extractOutput(text) {
528
+ const match = text.match(/```(?:json)?\s*([\s\S]*?)```/);
529
+ const json = match ? match[1].trim() : text.trim();
530
+ try {
531
+ const parsed = JSON.parse(json);
532
+ if (this.config.data.output && parsed !== null && typeof parsed !== "object") {
533
+ const fields = Object.keys(this.config.data.output);
534
+ if (fields.length === 1) {
535
+ return { [fields[0]]: parsed };
536
+ }
537
+ }
538
+ return parsed;
539
+ } catch {
540
+ if (this.config.data.output) {
541
+ const fields = Object.keys(this.config.data.output);
542
+ if (fields.length === 1) {
543
+ const strictMatch = json.trim().match(/^"([^"]*)"$/);
544
+ if (strictMatch) {
545
+ return { [fields[0]]: strictMatch[1] };
546
+ }
547
+ const lenientMatch = json.match(/"([^"]+)"/);
548
+ if (lenientMatch) {
549
+ return { [fields[0]]: lenientMatch[1] };
550
+ }
551
+ if (json.trim()) {
552
+ return { [fields[0]]: json.trim() };
553
+ }
554
+ }
555
+ }
556
+ return { content: text };
557
+ }
558
+ }
559
+ };
560
+
561
+ // src/flatmachine.ts
562
+ var yaml3 = __toESM(require("yaml"));
563
+ var import_fs5 = require("fs");
564
+ var import_path5 = require("path");
565
+ var import_node_crypto = require("crypto");
566
+
567
+ // src/execution.ts
568
+ var DefaultExecution = class {
569
+ async execute(fn) {
570
+ return fn();
571
+ }
572
+ };
573
+ var RetryExecution = class {
574
+ constructor(backoffs = [2, 8, 16, 35], jitter = 0.1) {
575
+ this.backoffs = backoffs;
576
+ this.jitter = jitter;
577
+ }
578
+ async execute(fn) {
579
+ let lastError;
580
+ for (let i = 0; i <= this.backoffs.length; i++) {
581
+ try {
582
+ return await fn();
583
+ } catch (err) {
584
+ lastError = err;
585
+ if (i < this.backoffs.length) {
586
+ const delay = this.backoffs[i] * (1 + this.jitter * (Math.random() * 2 - 1));
587
+ await new Promise((r) => setTimeout(r, delay * 1e3));
588
+ }
589
+ }
590
+ }
591
+ throw lastError;
592
+ }
593
+ };
594
+ var ParallelExecution = class {
595
+ constructor(nSamples = 3) {
596
+ this.nSamples = nSamples;
597
+ }
598
+ async execute(fn) {
599
+ const tasks = Array.from({ length: this.nSamples }, () => fn());
600
+ const results = await Promise.allSettled(tasks);
601
+ const valid = results.filter((r) => r.status === "fulfilled").map((r) => r.value);
602
+ if (!valid.length) {
603
+ throw new Error("Parallel execution produced no successful results");
604
+ }
605
+ return { results: valid, count: valid.length };
606
+ }
607
+ };
608
+ var MDAPVotingExecution = class {
609
+ constructor(kMargin = 3, maxCandidates = 10) {
610
+ this.kMargin = kMargin;
611
+ this.maxCandidates = maxCandidates;
612
+ }
613
+ async execute(fn) {
614
+ const votes = /* @__PURE__ */ new Map();
615
+ let samples = 0;
616
+ while (samples < this.maxCandidates) {
617
+ const value = await fn();
618
+ const key = JSON.stringify(value ?? null);
619
+ const entry = votes.get(key) ?? { value, count: 0 };
620
+ entry.count += 1;
621
+ votes.set(key, entry);
622
+ samples += 1;
623
+ const sorted = [...votes.values()].sort((a, b) => b.count - a.count);
624
+ const leader = sorted[0];
625
+ const runnerUp = sorted[1];
626
+ if (leader && leader.count - (runnerUp?.count ?? 0) >= this.kMargin) {
627
+ return leader.value;
628
+ }
629
+ }
630
+ const winner = [...votes.values()].sort((a, b) => b.count - a.count)[0];
631
+ return winner?.value;
632
+ }
633
+ };
634
+ function getExecutionType(config) {
635
+ if (config?.type === "retry") return new RetryExecution(config.backoffs, config.jitter);
636
+ if (config?.type === "parallel") return new ParallelExecution(config.n_samples ?? 3);
637
+ if (config?.type === "mdap_voting") return new MDAPVotingExecution(config.k_margin ?? 3, config.max_candidates ?? 10);
638
+ return new DefaultExecution();
639
+ }
640
+
641
+ // src/expression.ts
642
+ function evaluate(expr, ctx) {
643
+ const trimmed = expr.trim();
644
+ if (!trimmed) {
645
+ throw new Error("Empty expression");
646
+ }
647
+ const parser = new ExpressionParser(trimmed);
648
+ const result = parser.parse(ctx);
649
+ return result;
650
+ }
651
+ var ExpressionParser = class {
652
+ constructor(expr) {
653
+ this.tokens = [];
654
+ this.pos = 0;
655
+ this.tokens = this.tokenize(expr);
656
+ }
657
+ tokenize(expr) {
658
+ const tokens = [];
659
+ let current = "";
660
+ let inString = false;
661
+ let stringChar = "";
662
+ for (let i = 0; i < expr.length; i++) {
663
+ const char = expr[i];
664
+ if ((char === '"' || char === "'") && !inString) {
665
+ if (current) tokens.push(current);
666
+ current = char;
667
+ inString = true;
668
+ stringChar = char;
669
+ continue;
670
+ }
671
+ if (inString) {
672
+ current += char;
673
+ if (char === stringChar) {
674
+ tokens.push(current);
675
+ current = "";
676
+ inString = false;
677
+ }
678
+ continue;
679
+ }
680
+ const char2 = expr.slice(i, i + 2);
681
+ if (char2 === "==" || char2 === "!=" || char2 === "<=" || char2 === ">=") {
682
+ if (current) tokens.push(current);
683
+ tokens.push(char2);
684
+ current = "";
685
+ i++;
686
+ } else if ("<>()".includes(char)) {
687
+ if (current) tokens.push(current);
688
+ tokens.push(char);
689
+ current = "";
690
+ } else if (" \n".includes(char)) {
691
+ if (current) tokens.push(current);
692
+ current = "";
693
+ } else {
694
+ current += char;
695
+ }
696
+ }
697
+ if (current) tokens.push(current);
698
+ return tokens;
699
+ }
700
+ peek() {
701
+ return this.tokens[this.pos];
702
+ }
703
+ consume() {
704
+ return this.tokens[this.pos++];
705
+ }
706
+ parse(ctx) {
707
+ const result = this.parseExpression(ctx);
708
+ if (this.pos < this.tokens.length) {
709
+ throw new Error(`Unexpected token: ${this.tokens[this.pos]}`);
710
+ }
711
+ return result;
712
+ }
713
+ parseExpression(ctx) {
714
+ return this.parseOr(ctx);
715
+ }
716
+ parseOr(ctx) {
717
+ let left = this.parseAnd(ctx);
718
+ while (this.peek() === "or") {
719
+ this.consume();
720
+ const right = this.parseAnd(ctx);
721
+ left = left || right;
722
+ }
723
+ return left;
724
+ }
725
+ parseAnd(ctx) {
726
+ let left = this.parseNot(ctx);
727
+ while (this.peek() === "and") {
728
+ this.consume();
729
+ const right = this.parseNot(ctx);
730
+ left = left && right;
731
+ }
732
+ return left;
733
+ }
734
+ parseNot(ctx) {
735
+ if (this.peek() === "not") {
736
+ this.consume();
737
+ return !this.parseComparison(ctx);
738
+ }
739
+ return this.parseComparison(ctx);
740
+ }
741
+ parseComparison(ctx) {
742
+ const left = this.parsePrimary(ctx);
743
+ const op = this.peek();
744
+ if (op && ["==", "!=", "<", "<=", ">", ">="].includes(op)) {
745
+ this.consume();
746
+ const right = this.parsePrimary(ctx);
747
+ switch (op) {
748
+ case "==":
749
+ return left === right;
750
+ case "!=":
751
+ return left !== right;
752
+ case "<":
753
+ return left < right;
754
+ case "<=":
755
+ return left <= right;
756
+ case ">":
757
+ return left > right;
758
+ case ">=":
759
+ return left >= right;
760
+ default:
761
+ throw new Error(`Unknown operator: ${op}`);
762
+ }
763
+ }
764
+ return left;
765
+ }
766
+ parsePrimary(ctx) {
767
+ const token = this.consume();
768
+ if (token === void 0) {
769
+ throw new Error("Unexpected end of expression");
770
+ }
771
+ if (token === ")") {
772
+ throw new Error("Unexpected token: )");
773
+ }
774
+ if (token === "(") {
775
+ const value = this.parseExpression(ctx);
776
+ if (this.consume() !== ")") {
777
+ throw new Error("Expected closing parenthesis");
778
+ }
779
+ return value;
780
+ }
781
+ if (token.startsWith('"') && token.endsWith('"') || token.startsWith("'") && token.endsWith("'")) {
782
+ return token.slice(1, -1);
783
+ }
784
+ if (token === "true") return true;
785
+ if (token === "false") return false;
786
+ if (token === "null") return null;
787
+ if (!isNaN(Number(token))) {
788
+ return Number(token);
789
+ }
790
+ if (token.includes(".")) {
791
+ const parts = token.split(".");
792
+ let obj = ctx;
793
+ for (const part of parts) {
794
+ if (obj && typeof obj === "object" && part in obj) {
795
+ obj = obj[part];
796
+ } else {
797
+ return void 0;
798
+ }
799
+ }
800
+ return obj;
801
+ }
802
+ return ctx[token];
803
+ }
804
+ };
805
+
806
+ // src/persistence.ts
807
+ var import_fs3 = require("fs");
808
+ var import_path3 = require("path");
809
+ var MemoryBackend = class {
810
+ constructor() {
811
+ this.store = /* @__PURE__ */ new Map();
812
+ }
813
+ async save(key, snapshot) {
814
+ this.store.set(key, snapshot);
815
+ }
816
+ async load(key) {
817
+ return this.store.get(key) ?? null;
818
+ }
819
+ async delete(key) {
820
+ this.store.delete(key);
821
+ }
822
+ async list(prefix) {
823
+ return [...this.store.keys()].filter((k) => k.startsWith(prefix)).sort();
824
+ }
825
+ };
826
+ var LocalFileBackend = class {
827
+ constructor(dir = ".checkpoints") {
828
+ this.dir = dir;
829
+ }
830
+ async ensureDir(path) {
831
+ try {
832
+ await import_fs3.promises.mkdir((0, import_path3.dirname)(path), { recursive: true });
833
+ } catch (error) {
834
+ }
835
+ }
836
+ getPath(key) {
837
+ return (0, import_path3.join)(this.dir, `${key}.json`);
838
+ }
839
+ async save(key, snapshot) {
840
+ const path = this.getPath(key);
841
+ await this.ensureDir(path);
842
+ const tempPath = `${path}.tmp`;
843
+ await import_fs3.promises.writeFile(tempPath, JSON.stringify(snapshot, null, 2));
844
+ await import_fs3.promises.rename(tempPath, path);
845
+ }
846
+ async load(key) {
847
+ try {
848
+ const path = this.getPath(key);
849
+ const data = await import_fs3.promises.readFile(path, "utf-8");
850
+ return JSON.parse(data);
851
+ } catch (error) {
852
+ return null;
853
+ }
854
+ }
855
+ async delete(key) {
856
+ try {
857
+ const path = this.getPath(key);
858
+ await import_fs3.promises.unlink(path);
859
+ } catch (error) {
860
+ }
861
+ }
862
+ async list(prefix) {
863
+ try {
864
+ const files = await import_fs3.promises.readdir(this.dir, { recursive: true });
865
+ return files.filter((file) => file.endsWith(".json")).map((file) => file.replace(".json", "")).filter((key) => key.startsWith(prefix)).sort();
866
+ } catch (error) {
867
+ return [];
868
+ }
869
+ }
870
+ };
871
+ var CheckpointManager = class {
872
+ constructor(backend) {
873
+ this.backend = backend;
874
+ }
875
+ async checkpoint(snapshot) {
876
+ const key = `${snapshot.execution_id}/step_${String(snapshot.step).padStart(6, "0")}`;
877
+ await this.backend.save(key, snapshot);
878
+ }
879
+ async restore(executionId) {
880
+ const keys = await this.backend.list(executionId);
881
+ if (!keys.length) return null;
882
+ const sortedKeys = keys.sort((a, b) => {
883
+ const stepA = parseInt(a.split("_")[1] || "0");
884
+ const stepB = parseInt(b.split("_")[1] || "0");
885
+ return stepA - stepB;
886
+ });
887
+ return this.backend.load(sortedKeys[sortedKeys.length - 1]);
888
+ }
889
+ };
890
+
891
+ // src/results.ts
892
+ var TimeoutError = class extends Error {
893
+ constructor(message) {
894
+ super(message);
895
+ this.name = "TimeoutError";
896
+ }
897
+ };
898
+ var store = /* @__PURE__ */ new Map();
899
+ var waiters = /* @__PURE__ */ new Map();
900
+ var addWaiter = (uri, waiter) => {
901
+ const current = waiters.get(uri) ?? /* @__PURE__ */ new Set();
902
+ current.add(waiter);
903
+ waiters.set(uri, current);
904
+ };
905
+ var removeWaiter = (uri, waiter) => {
906
+ const current = waiters.get(uri);
907
+ if (!current) return;
908
+ current.delete(waiter);
909
+ if (current.size === 0) waiters.delete(uri);
910
+ };
911
+ var notifyWaiters = (uri, data) => {
912
+ const current = waiters.get(uri);
913
+ if (!current) return;
914
+ for (const waiter of current) {
915
+ if (waiter.timeoutId) clearTimeout(waiter.timeoutId);
916
+ waiter.resolve(data);
917
+ }
918
+ waiters.delete(uri);
919
+ };
920
+ var inMemoryResultBackend = {
921
+ async write(uri, data) {
922
+ store.set(uri, data);
923
+ notifyWaiters(uri, data);
924
+ },
925
+ async read(uri, opts) {
926
+ if (!opts?.block) {
927
+ return store.get(uri);
928
+ }
929
+ if (store.has(uri)) {
930
+ return store.get(uri);
931
+ }
932
+ return new Promise((resolve2, reject) => {
933
+ const waiter = { resolve: resolve2, reject };
934
+ addWaiter(uri, waiter);
935
+ if (opts.timeout !== void 0) {
936
+ waiter.timeoutId = setTimeout(() => {
937
+ removeWaiter(uri, waiter);
938
+ reject(new TimeoutError(`Timed out waiting for ${uri}`));
939
+ }, opts.timeout);
940
+ }
941
+ });
942
+ },
943
+ async exists(uri) {
944
+ return store.has(uri);
945
+ },
946
+ async delete(uri) {
947
+ store.delete(uri);
948
+ }
949
+ };
950
+
951
+ // src/locking.ts
952
+ var import_fs4 = require("fs");
953
+ var import_path4 = require("path");
954
+ var NoOpLock = class {
955
+ async acquire(key) {
956
+ return true;
957
+ }
958
+ async release(key) {
959
+ }
960
+ };
961
+ var LocalFileLock = class {
962
+ constructor(lockDir = ".locks") {
963
+ this.handles = /* @__PURE__ */ new Map();
964
+ this.lockDir = lockDir;
965
+ }
966
+ getLockPath(key) {
967
+ const safeKey = key.replace(/[^a-zA-Z0-9_-]/g, "_");
968
+ return (0, import_path4.join)(this.lockDir, `${safeKey}.lock`);
969
+ }
970
+ async ensureDir() {
971
+ try {
972
+ await import_fs4.promises.mkdir(this.lockDir, { recursive: true });
973
+ } catch (error) {
974
+ }
975
+ }
976
+ async acquire(key) {
977
+ await this.ensureDir();
978
+ const lockPath = this.getLockPath(key);
979
+ try {
980
+ const handle = await import_fs4.promises.open(lockPath, "wx");
981
+ await handle.write(JSON.stringify({
982
+ pid: process.pid,
983
+ key,
984
+ acquired_at: (/* @__PURE__ */ new Date()).toISOString()
985
+ }));
986
+ this.handles.set(key, handle);
987
+ return true;
988
+ } catch (error) {
989
+ if (error.code === "EEXIST") {
990
+ return false;
991
+ }
992
+ throw error;
993
+ }
994
+ }
995
+ async release(key) {
996
+ const handle = this.handles.get(key);
997
+ const lockPath = this.getLockPath(key);
998
+ if (handle) {
999
+ await handle.close();
1000
+ this.handles.delete(key);
1001
+ }
1002
+ try {
1003
+ await import_fs4.promises.unlink(lockPath);
1004
+ } catch (error) {
1005
+ }
1006
+ }
1007
+ };
1008
+
1009
+ // src/flatmachine.ts
1010
+ var FlatMachine = class _FlatMachine {
1011
+ constructor(options) {
1012
+ this.executionId = (0, import_node_crypto.randomUUID)();
1013
+ this.agents = /* @__PURE__ */ new Map();
1014
+ this.context = {};
1015
+ this.input = {};
1016
+ this.checkpointEvents = /* @__PURE__ */ new Set();
1017
+ this.pendingLaunches = [];
1018
+ this.currentStep = 0;
1019
+ this.config = typeof options.config === "string" ? yaml3.parse((0, import_fs5.readFileSync)(options.config, "utf-8")) : options.config;
1020
+ this.hooks = options.hooks;
1021
+ this.configDir = options.configDir ?? process.cwd();
1022
+ this.profilesFile = this.resolveProfilesFile(options.profilesFile);
1023
+ this.executionId = options.executionId ?? this.executionId;
1024
+ this.parentExecutionId = options.parentExecutionId;
1025
+ const backendConfig = this.config.data.settings?.backends;
1026
+ this.resultBackend = options.resultBackend ?? this.createResultBackend(backendConfig);
1027
+ this.executionLock = options.executionLock ?? this.createExecutionLock(backendConfig);
1028
+ if (options.persistence) {
1029
+ this.checkpointManager = new CheckpointManager(options.persistence);
1030
+ } else if (this.config.data.persistence?.enabled) {
1031
+ const backend = this.createPersistenceBackend(this.config.data.persistence);
1032
+ this.checkpointManager = new CheckpointManager(backend);
1033
+ } else if (backendConfig?.persistence) {
1034
+ const backend = this.createSettingsPersistenceBackend(backendConfig.persistence);
1035
+ this.checkpointManager = new CheckpointManager(backend);
1036
+ }
1037
+ if (this.checkpointManager) {
1038
+ const configEvents = this.config.data.persistence?.checkpoint_on;
1039
+ const events = configEvents?.length ? configEvents : ["execute"];
1040
+ this.checkpointEvents = new Set(events);
1041
+ }
1042
+ }
1043
+ async execute(input, resumeSnapshot) {
1044
+ if (this.config.data.expression_engine === "cel") {
1045
+ throw new Error("expression_engine 'cel' is not supported in the JS SDK yet");
1046
+ }
1047
+ const lockKey = resumeSnapshot?.execution_id ?? this.executionId;
1048
+ const lockAcquired = await this.executionLock.acquire(lockKey);
1049
+ if (!lockAcquired) {
1050
+ throw new Error(`Execution ${lockKey} is already running`);
1051
+ }
1052
+ try {
1053
+ return await this.executeInternal(input, resumeSnapshot);
1054
+ } finally {
1055
+ await this.executionLock.release(lockKey);
1056
+ }
1057
+ }
1058
+ async executeInternal(input, resumeSnapshot) {
1059
+ let state;
1060
+ let steps;
1061
+ if (resumeSnapshot) {
1062
+ this.executionId = resumeSnapshot.execution_id;
1063
+ this.parentExecutionId = resumeSnapshot.parent_execution_id;
1064
+ this.context = resumeSnapshot.context;
1065
+ state = resumeSnapshot.current_state;
1066
+ steps = resumeSnapshot.step;
1067
+ this.pendingLaunches = resumeSnapshot.pending_launches ?? [];
1068
+ if (this.pendingLaunches.length) {
1069
+ await this.resumePendingLaunches();
1070
+ }
1071
+ } else {
1072
+ this.input = input ?? {};
1073
+ this.context = this.render(this.config.data.context ?? {}, { input: this.input });
1074
+ this.context = await this.hooks?.onMachineStart?.(this.context) ?? this.context;
1075
+ state = this.findInitialState();
1076
+ steps = 0;
1077
+ this.pendingLaunches = [];
1078
+ if (this.shouldCheckpoint("machine_start")) {
1079
+ await this.checkpoint(state, steps, "machine_start");
1080
+ }
1081
+ }
1082
+ const maxSteps = this.config.data.settings?.max_steps ?? 100;
1083
+ while (steps++ < maxSteps) {
1084
+ const def = this.config.data.states[state];
1085
+ this.currentState = state;
1086
+ this.currentStep = steps;
1087
+ this.context = await this.hooks?.onStateEnter?.(state, this.context) ?? this.context;
1088
+ if (this.shouldCheckpoint("execute")) {
1089
+ await this.checkpoint(state, steps, "execute");
1090
+ }
1091
+ if (def.type === "final") {
1092
+ const output2 = this.render(def.output ?? {}, { context: this.context, input: this.input });
1093
+ await this.resultBackend?.write(`flatagents://${this.executionId}/result`, output2);
1094
+ if (this.shouldCheckpoint("machine_end")) {
1095
+ await this.checkpoint(state, steps, "machine_end", output2);
1096
+ }
1097
+ return await this.hooks?.onMachineEnd?.(this.context, output2) ?? output2;
1098
+ }
1099
+ let output;
1100
+ const executor = getExecutionType(def.execution);
1101
+ try {
1102
+ if (def.action) {
1103
+ const actionResult = await this.hooks?.onAction?.(def.action, this.context);
1104
+ if (actionResult !== void 0) {
1105
+ this.context = actionResult;
1106
+ output = actionResult;
1107
+ }
1108
+ }
1109
+ if (def.agent) {
1110
+ output = await executor.execute(() => this.executeAgent(def));
1111
+ } else if (def.machine) {
1112
+ output = await this.executeMachine(def);
1113
+ }
1114
+ } catch (err) {
1115
+ this.context.last_error = err.message;
1116
+ this.context.last_error_type = err.name || err.constructor?.name;
1117
+ const recovery = await this.hooks?.onError?.(state, err, this.context);
1118
+ if (recovery) {
1119
+ state = recovery;
1120
+ continue;
1121
+ }
1122
+ if (def.on_error) {
1123
+ if (typeof def.on_error === "string") {
1124
+ state = def.on_error;
1125
+ continue;
1126
+ }
1127
+ const errorKey = this.context.last_error_type;
1128
+ const nextState = def.on_error[errorKey] ?? def.on_error.default;
1129
+ if (nextState) {
1130
+ state = nextState;
1131
+ continue;
1132
+ }
1133
+ }
1134
+ throw err;
1135
+ }
1136
+ if (def.output_to_context) {
1137
+ Object.assign(this.context, this.render(def.output_to_context, { context: this.context, input: this.input, output }));
1138
+ }
1139
+ if (def.launch) await this.launchMachines(def);
1140
+ output = await this.hooks?.onStateExit?.(state, this.context, output) ?? output;
1141
+ const next = this.evaluateTransitions(def, output);
1142
+ state = await this.hooks?.onTransition?.(state, next, this.context) ?? next;
1143
+ }
1144
+ throw new Error("Max steps exceeded");
1145
+ }
1146
+ async resume(executionId) {
1147
+ const snapshot = await this.checkpointManager?.restore(executionId);
1148
+ if (!snapshot) throw new Error(`No checkpoint for ${executionId}`);
1149
+ return this.execute(void 0, snapshot);
1150
+ }
1151
+ findInitialState() {
1152
+ for (const [name, state] of Object.entries(this.config.data.states)) {
1153
+ if (state.type === "initial") return name;
1154
+ }
1155
+ return Object.keys(this.config.data.states)[0];
1156
+ }
1157
+ async executeAgent(def) {
1158
+ let agent = this.agents.get(def.agent);
1159
+ if (!agent) {
1160
+ const agentRef = this.config.data.agents?.[def.agent] ?? def.agent;
1161
+ agent = this.createAgent(agentRef);
1162
+ this.agents.set(def.agent, agent);
1163
+ }
1164
+ const input = this.render(def.input ?? {}, { context: this.context, input: this.input });
1165
+ const result = await agent.call(input);
1166
+ return result.output;
1167
+ }
1168
+ async executeMachine(def) {
1169
+ const machineDefs = Array.isArray(def.machine) ? def.machine : [def.machine];
1170
+ const mode = def.mode ?? "settled";
1171
+ const timeoutMs = def.timeout && def.timeout > 0 ? def.timeout * 1e3 : void 0;
1172
+ if (def.foreach) {
1173
+ const items = this.render({ items: def.foreach }, { context: this.context, input: this.input }).items;
1174
+ const varName = def.as ?? "item";
1175
+ const tasks = items.map(async (item, index) => {
1176
+ const input2 = this.render(def.input ?? {}, { context: this.context, input: this.input, [varName]: item });
1177
+ const result = await this.invokeMachineSingle(machineDefs[0], input2, timeoutMs);
1178
+ const keyValue = def.key ? this.render(def.key, { context: this.context, input: this.input, [varName]: item, output: result }) : void 0;
1179
+ return { index, key: keyValue, result };
1180
+ });
1181
+ const output = await this.awaitWithMode(tasks, mode);
1182
+ if (mode === "any") {
1183
+ const picked = output;
1184
+ if (def.key) return { [String(picked.key)]: picked.result };
1185
+ return picked.result;
1186
+ }
1187
+ const settled = output;
1188
+ if (def.key) {
1189
+ const keyed = {};
1190
+ for (const entry of settled) {
1191
+ keyed[String(entry.key)] = entry.result;
1192
+ }
1193
+ return keyed;
1194
+ }
1195
+ const ordered = new Array(items.length);
1196
+ for (const entry of settled) {
1197
+ ordered[entry.index] = entry.result;
1198
+ }
1199
+ return ordered;
1200
+ }
1201
+ if (machineDefs.length > 1 || machineDefs.length === 1 && typeof machineDefs[0] === "object" && "name" in machineDefs[0]) {
1202
+ const tasks = machineDefs.map(async (entry) => {
1203
+ const name = this.getMachineName(entry);
1204
+ const baseInput = this.render(def.input ?? {}, { context: this.context, input: this.input });
1205
+ const entryInput = typeof entry === "string" ? {} : this.render(entry.input ?? {}, { context: this.context, input: this.input });
1206
+ const mergedInput = { ...baseInput, ...entryInput };
1207
+ const result = await this.invokeMachineSingle(entry, mergedInput, timeoutMs);
1208
+ return { name, result };
1209
+ });
1210
+ const output = await this.awaitWithMode(tasks, mode);
1211
+ if (mode === "any") {
1212
+ const picked = output;
1213
+ return { [picked.name]: picked.result };
1214
+ }
1215
+ const settled = output;
1216
+ return settled.reduce((acc, entry) => {
1217
+ acc[entry.name] = entry.result;
1218
+ return acc;
1219
+ }, {});
1220
+ }
1221
+ const input = this.render(def.input ?? {}, { context: this.context, input: this.input });
1222
+ return this.invokeMachineSingle(machineDefs[0], input, timeoutMs);
1223
+ }
1224
+ async launchMachines(def) {
1225
+ const machines = Array.isArray(def.launch) ? def.launch : [def.launch];
1226
+ const input = this.render(def.launch_input ?? {}, { context: this.context, input: this.input });
1227
+ await Promise.all(machines.map((machineRef) => this.launchFireAndForget(machineRef, input)));
1228
+ }
1229
+ evaluateTransitions(def, output) {
1230
+ if (!def.transitions?.length) throw new Error("No transitions defined");
1231
+ for (const t of def.transitions) {
1232
+ if (!t.condition || evaluate(t.condition, { context: this.context, input: this.input, output })) {
1233
+ return t.to;
1234
+ }
1235
+ }
1236
+ throw new Error("No matching transition");
1237
+ }
1238
+ async checkpoint(state, step, event, output) {
1239
+ if (!this.checkpointManager) return;
1240
+ await this.checkpointManager.checkpoint({
1241
+ execution_id: this.executionId,
1242
+ machine_name: this.config.data.name ?? "unnamed",
1243
+ spec_version: this.config.spec_version ?? "0.4.0",
1244
+ current_state: state,
1245
+ context: this.context,
1246
+ step,
1247
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
1248
+ event,
1249
+ output,
1250
+ parent_execution_id: this.parentExecutionId,
1251
+ pending_launches: this.pendingLaunches.length ? this.pendingLaunches : void 0
1252
+ });
1253
+ }
1254
+ render(template, vars) {
1255
+ if (typeof template === "string") {
1256
+ const directValue = this.renderDirectValue(template, vars);
1257
+ if (directValue !== void 0) return directValue;
1258
+ const rendered = renderTemplate(template, vars, "flatmachine");
1259
+ try {
1260
+ return JSON.parse(rendered);
1261
+ } catch {
1262
+ return rendered;
1263
+ }
1264
+ }
1265
+ if (Array.isArray(template)) return template.map((t) => this.render(t, vars));
1266
+ if (typeof template === "object" && template !== null) {
1267
+ return Object.fromEntries(Object.entries(template).map(([k, v]) => [k, this.render(v, vars)]));
1268
+ }
1269
+ return template;
1270
+ }
1271
+ renderDirectValue(template, vars) {
1272
+ const match = template.match(/^\s*{{\s*([^}]+)\s*}}\s*$/);
1273
+ if (!match) return void 0;
1274
+ const expr = match[1].trim();
1275
+ if (!/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z0-9_]+)*$/.test(expr)) return void 0;
1276
+ return this.resolvePath(vars, expr);
1277
+ }
1278
+ resolvePath(vars, expr) {
1279
+ return expr.split(".").reduce((obj, part) => obj ? obj[part] : void 0, vars);
1280
+ }
1281
+ shouldCheckpoint(event) {
1282
+ return this.checkpointManager ? this.checkpointEvents.has(event) : false;
1283
+ }
1284
+ createResultBackend(config) {
1285
+ if (!config?.results || config.results === "memory") return inMemoryResultBackend;
1286
+ throw new Error(`Unsupported result backend: ${config.results}`);
1287
+ }
1288
+ createExecutionLock(config) {
1289
+ if (!config?.locking || config.locking === "none") return new NoOpLock();
1290
+ if (config.locking === "local") return new LocalFileLock();
1291
+ throw new Error(`Unsupported execution lock backend: ${config.locking}`);
1292
+ }
1293
+ createSettingsPersistenceBackend(setting) {
1294
+ if (setting === "memory") return new MemoryBackend();
1295
+ if (setting === "local") return new LocalFileBackend();
1296
+ throw new Error(`Unsupported persistence backend: ${setting}`);
1297
+ }
1298
+ createPersistenceBackend(config) {
1299
+ if (config.backend === "memory") return new MemoryBackend();
1300
+ if (config.backend === "local") return new LocalFileBackend();
1301
+ throw new Error(`Unsupported persistence backend: ${config.backend}`);
1302
+ }
1303
+ createAgent(agentRef) {
1304
+ if (agentRef && typeof agentRef === "object") {
1305
+ if (agentRef.spec === "flatagent" && agentRef.data) {
1306
+ return new FlatAgent({ config: agentRef, profilesFile: this.profilesFile });
1307
+ }
1308
+ if (agentRef.path) {
1309
+ return new FlatAgent({
1310
+ config: `${this.configDir}/${agentRef.path}`,
1311
+ profilesFile: this.profilesFile
1312
+ });
1313
+ }
1314
+ }
1315
+ return new FlatAgent({
1316
+ config: `${this.configDir}/${agentRef}`,
1317
+ profilesFile: this.profilesFile
1318
+ });
1319
+ }
1320
+ createMachine(machineRef, overrides) {
1321
+ const resolved = this.resolveMachineConfig(machineRef);
1322
+ return new _FlatMachine({
1323
+ config: resolved.config,
1324
+ configDir: resolved.configDir,
1325
+ resultBackend: this.resultBackend,
1326
+ hooks: this.hooks,
1327
+ executionId: overrides?.executionId,
1328
+ parentExecutionId: overrides?.parentExecutionId,
1329
+ profilesFile: this.profilesFile
1330
+ });
1331
+ }
1332
+ resolveMachineConfig(machineRef) {
1333
+ if (typeof machineRef === "object" && machineRef) {
1334
+ if ("spec" in machineRef && "data" in machineRef) {
1335
+ return { config: machineRef, configDir: this.configDir };
1336
+ }
1337
+ if ("path" in machineRef && machineRef.path) {
1338
+ return this.resolveMachinePath(String(machineRef.path));
1339
+ }
1340
+ if ("inline" in machineRef && machineRef.inline) {
1341
+ return { config: machineRef.inline, configDir: this.configDir };
1342
+ }
1343
+ if ("name" in machineRef) {
1344
+ return this.resolveMachineConfig(machineRef.name);
1345
+ }
1346
+ }
1347
+ const name = String(machineRef);
1348
+ const entry = this.config.data.machines?.[name];
1349
+ if (entry && typeof entry === "object") {
1350
+ if ("path" in entry && entry.path) {
1351
+ return this.resolveMachinePath(String(entry.path));
1352
+ }
1353
+ if ("inline" in entry && entry.inline) {
1354
+ return { config: entry.inline, configDir: this.configDir };
1355
+ }
1356
+ if ("spec" in entry && "data" in entry) {
1357
+ return { config: entry, configDir: this.configDir };
1358
+ }
1359
+ }
1360
+ if (typeof entry === "string") {
1361
+ return this.resolveMachinePath(entry);
1362
+ }
1363
+ return this.resolveMachinePath(name);
1364
+ }
1365
+ resolveMachinePath(pathRef) {
1366
+ const resolved = (0, import_path5.resolve)(this.configDir, pathRef);
1367
+ return { config: resolved, configDir: (0, import_path5.dirname)(resolved) };
1368
+ }
1369
+ resolveProfilesFile(explicitPath) {
1370
+ const configProfiles = this.config?.data?.profiles;
1371
+ if (typeof configProfiles === "string" && configProfiles.trim().length > 0) {
1372
+ return (0, import_path5.resolve)(this.configDir, configProfiles);
1373
+ }
1374
+ const discovered = (0, import_path5.resolve)(this.configDir, "profiles.yml");
1375
+ if ((0, import_fs5.existsSync)(discovered)) return discovered;
1376
+ return explicitPath;
1377
+ }
1378
+ getMachineName(machineRef) {
1379
+ if (typeof machineRef === "string") return machineRef;
1380
+ if (machineRef?.name) return String(machineRef.name);
1381
+ if (machineRef?.path) return String(machineRef.path);
1382
+ if (machineRef?.inline?.data?.name) return String(machineRef.inline.data.name);
1383
+ if (machineRef?.spec === "flatmachine" && machineRef.data?.name) return String(machineRef.data.name);
1384
+ return "machine";
1385
+ }
1386
+ makeResultUri(executionId) {
1387
+ return `flatagents://${executionId}/result`;
1388
+ }
1389
+ async addPendingLaunch(executionId, machine, input) {
1390
+ this.pendingLaunches.push({ execution_id: executionId, machine, input, launched: false });
1391
+ if (this.currentState && this.shouldCheckpoint("execute")) {
1392
+ await this.checkpoint(this.currentState, this.currentStep, "execute");
1393
+ }
1394
+ }
1395
+ markLaunched(executionId) {
1396
+ for (const intent of this.pendingLaunches) {
1397
+ if (intent.execution_id === executionId) {
1398
+ intent.launched = true;
1399
+ return;
1400
+ }
1401
+ }
1402
+ }
1403
+ clearPendingLaunch(executionId) {
1404
+ this.pendingLaunches = this.pendingLaunches.filter((intent) => intent.execution_id !== executionId);
1405
+ }
1406
+ async resumePendingLaunches() {
1407
+ if (!this.resultBackend) return;
1408
+ for (const intent of this.pendingLaunches) {
1409
+ if (intent.launched) continue;
1410
+ const uri = this.makeResultUri(intent.execution_id);
1411
+ const exists = await this.resultBackend.exists(uri);
1412
+ if (exists) continue;
1413
+ const launchPromise = this.launchAndWrite(intent.machine, intent.execution_id, intent.input);
1414
+ this.markLaunched(intent.execution_id);
1415
+ launchPromise.then(() => this.clearPendingLaunch(intent.execution_id)).catch(() => {
1416
+ });
1417
+ }
1418
+ }
1419
+ async launchAndWrite(machineRef, executionId, input) {
1420
+ const machine = this.createMachine(machineRef, {
1421
+ executionId,
1422
+ parentExecutionId: this.executionId
1423
+ });
1424
+ try {
1425
+ const result = await machine.execute(input);
1426
+ if (this.resultBackend) {
1427
+ await this.resultBackend.write(this.makeResultUri(executionId), result);
1428
+ }
1429
+ return result;
1430
+ } catch (err) {
1431
+ if (this.resultBackend) {
1432
+ const error = err;
1433
+ await this.resultBackend.write(this.makeResultUri(executionId), {
1434
+ _error: error.message,
1435
+ _error_type: error.name || error.constructor?.name
1436
+ });
1437
+ }
1438
+ throw err;
1439
+ }
1440
+ }
1441
+ normalizeMachineResult(result) {
1442
+ if (result && typeof result === "object" && "_error" in result) {
1443
+ const error = new Error(String(result._error ?? "Machine execution failed"));
1444
+ error.name = String(result._error_type ?? "Error");
1445
+ throw error;
1446
+ }
1447
+ return result;
1448
+ }
1449
+ async invokeMachineSingle(machineRef, input, timeoutMs) {
1450
+ const childId = (0, import_node_crypto.randomUUID)();
1451
+ const machineName = this.getMachineName(machineRef);
1452
+ await this.addPendingLaunch(childId, machineName, input);
1453
+ const launchPromise = this.launchAndWrite(machineRef, childId, input);
1454
+ let shouldClear = false;
1455
+ try {
1456
+ if (!this.resultBackend) {
1457
+ const result2 = await launchPromise;
1458
+ shouldClear = true;
1459
+ return result2;
1460
+ }
1461
+ const result = await this.resultBackend.read(this.makeResultUri(childId), {
1462
+ block: true,
1463
+ timeout: timeoutMs
1464
+ });
1465
+ shouldClear = true;
1466
+ return this.normalizeMachineResult(result);
1467
+ } catch (err) {
1468
+ if (err.name !== "TimeoutError") {
1469
+ shouldClear = true;
1470
+ }
1471
+ throw err;
1472
+ } finally {
1473
+ this.markLaunched(childId);
1474
+ if (shouldClear) {
1475
+ this.clearPendingLaunch(childId);
1476
+ }
1477
+ launchPromise.catch(() => {
1478
+ });
1479
+ }
1480
+ }
1481
+ async launchFireAndForget(machineRef, input) {
1482
+ const childId = (0, import_node_crypto.randomUUID)();
1483
+ const machineName = this.getMachineName(machineRef);
1484
+ await this.addPendingLaunch(childId, machineName, input);
1485
+ const launchPromise = this.launchAndWrite(machineRef, childId, input);
1486
+ this.markLaunched(childId);
1487
+ launchPromise.then(() => this.clearPendingLaunch(childId)).catch(() => {
1488
+ });
1489
+ }
1490
+ async awaitWithMode(tasks, mode, timeoutMs) {
1491
+ if (tasks.length === 0) {
1492
+ return mode === "any" ? void 0 : [];
1493
+ }
1494
+ const runner = mode === "any" ? this.firstCompleted(tasks) : Promise.all(tasks);
1495
+ if (!timeoutMs) return runner;
1496
+ return this.withTimeout(runner, timeoutMs);
1497
+ }
1498
+ async firstCompleted(tasks) {
1499
+ return new Promise((resolve2, reject) => {
1500
+ let pending = tasks.length;
1501
+ let settled = false;
1502
+ const errors = [];
1503
+ for (const task of tasks) {
1504
+ task.then((value) => {
1505
+ if (settled) return;
1506
+ settled = true;
1507
+ resolve2(value);
1508
+ }).catch((err) => {
1509
+ errors.push(err);
1510
+ pending -= 1;
1511
+ if (pending === 0 && !settled) {
1512
+ reject(errors[0]);
1513
+ }
1514
+ });
1515
+ }
1516
+ });
1517
+ }
1518
+ withTimeout(promise, timeoutMs) {
1519
+ return new Promise((resolve2, reject) => {
1520
+ const timer = setTimeout(() => reject(new Error("Operation timed out")), timeoutMs);
1521
+ promise.then((value) => {
1522
+ clearTimeout(timer);
1523
+ resolve2(value);
1524
+ }).catch((err) => {
1525
+ clearTimeout(timer);
1526
+ reject(err);
1527
+ });
1528
+ });
1529
+ }
1530
+ pickFirstKeyedResult(results) {
1531
+ const firstKey = Object.keys(results)[0];
1532
+ if (!firstKey) return {};
1533
+ return { [firstKey]: results[firstKey] };
1534
+ }
1535
+ };
1536
+
1537
+ // src/hooks.ts
1538
+ var WebhookHooks = class {
1539
+ constructor(url) {
1540
+ this.url = url;
1541
+ }
1542
+ async send(event, data) {
1543
+ try {
1544
+ const body = JSON.stringify({ event, ...data, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, (key, value) => {
1545
+ if (typeof value === "object" && value !== null) {
1546
+ const seen = /* @__PURE__ */ new WeakSet();
1547
+ return JSON.parse(JSON.stringify(value, (k, v) => {
1548
+ if (typeof v === "object" && v !== null) {
1549
+ if (seen.has(v)) return "[Circular]";
1550
+ seen.add(v);
1551
+ }
1552
+ return v;
1553
+ }));
1554
+ }
1555
+ return value;
1556
+ });
1557
+ await fetch(this.url, {
1558
+ method: "POST",
1559
+ headers: { "Content-Type": "application/json" },
1560
+ body
1561
+ });
1562
+ } catch {
1563
+ }
1564
+ }
1565
+ async onMachineStart(context) {
1566
+ await this.send("machine_start", { context });
1567
+ return context;
1568
+ }
1569
+ async onMachineEnd(context, output) {
1570
+ await this.send("machine_end", { context, output });
1571
+ return output;
1572
+ }
1573
+ async onStateEnter(state, context) {
1574
+ await this.send("state_enter", { state, context });
1575
+ return context;
1576
+ }
1577
+ async onStateExit(state, context, output) {
1578
+ await this.send("state_exit", { state, context, output });
1579
+ return output;
1580
+ }
1581
+ async onAction(action, context) {
1582
+ await this.send("action", { action, context });
1583
+ return context;
1584
+ }
1585
+ };
1586
+ var CompositeHooks = class {
1587
+ constructor(hooks) {
1588
+ this.hooks = hooks;
1589
+ }
1590
+ async onMachineStart(context) {
1591
+ let result = context;
1592
+ for (const hook of this.hooks) {
1593
+ if (hook.onMachineStart) {
1594
+ try {
1595
+ result = await hook.onMachineStart(result);
1596
+ } catch {
1597
+ }
1598
+ }
1599
+ }
1600
+ return result;
1601
+ }
1602
+ async onMachineEnd(context, output) {
1603
+ let result = output;
1604
+ for (const hook of this.hooks) {
1605
+ if (hook.onMachineEnd) {
1606
+ try {
1607
+ result = await hook.onMachineEnd(context, result);
1608
+ } catch {
1609
+ }
1610
+ }
1611
+ }
1612
+ return result;
1613
+ }
1614
+ async onStateEnter(state, context) {
1615
+ let result = context;
1616
+ for (const hook of this.hooks) {
1617
+ if (hook.onStateEnter) {
1618
+ try {
1619
+ result = await hook.onStateEnter(state, result);
1620
+ } catch {
1621
+ }
1622
+ }
1623
+ }
1624
+ return result;
1625
+ }
1626
+ async onStateExit(state, context, output) {
1627
+ let result = output;
1628
+ for (const hook of this.hooks) {
1629
+ if (hook.onStateExit) {
1630
+ try {
1631
+ result = await hook.onStateExit(state, context, result);
1632
+ } catch {
1633
+ }
1634
+ }
1635
+ }
1636
+ return result;
1637
+ }
1638
+ async onTransition(from, to, context) {
1639
+ let result = to;
1640
+ for (const hook of this.hooks) {
1641
+ if (hook.onTransition) {
1642
+ try {
1643
+ result = await hook.onTransition(from, result, context);
1644
+ } catch {
1645
+ }
1646
+ }
1647
+ }
1648
+ return result;
1649
+ }
1650
+ async onError(state, error, context) {
1651
+ let result = null;
1652
+ for (const hook of this.hooks) {
1653
+ if (hook.onError) {
1654
+ try {
1655
+ const hookResult = await hook.onError(state, error, context);
1656
+ if (hookResult !== null) result = hookResult;
1657
+ } catch {
1658
+ }
1659
+ }
1660
+ }
1661
+ return result;
1662
+ }
1663
+ async onAction(action, context) {
1664
+ let result = context;
1665
+ for (const hook of this.hooks) {
1666
+ if (hook.onAction) {
1667
+ try {
1668
+ result = await hook.onAction(action, result);
1669
+ } catch {
1670
+ }
1671
+ }
1672
+ }
1673
+ return result;
1674
+ }
1675
+ };
1676
+ // Annotate the CommonJS export names for ESM import in node:
1677
+ 0 && (module.exports = {
1678
+ CheckpointManager,
1679
+ CompositeHooks,
1680
+ DefaultExecution,
1681
+ FlatAgent,
1682
+ FlatMachine,
1683
+ LocalFileBackend,
1684
+ LocalFileLock,
1685
+ MCPToolProvider,
1686
+ MDAPVotingExecution,
1687
+ MemoryBackend,
1688
+ MockLLMBackend,
1689
+ NoOpLock,
1690
+ ParallelExecution,
1691
+ ProfileManager,
1692
+ RetryExecution,
1693
+ VercelAIBackend,
1694
+ WebhookHooks,
1695
+ evaluate,
1696
+ getExecutionType,
1697
+ inMemoryResultBackend,
1698
+ resolveModelConfig,
1699
+ setTemplateAllowlist
1700
+ });