@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.
Files changed (127) hide show
  1. package/dist/agent.d.ts +92 -0
  2. package/dist/agent.d.ts.map +1 -0
  3. package/dist/agent.js +137 -0
  4. package/dist/agent.js.map +1 -0
  5. package/dist/cli-utils.d.ts +41 -0
  6. package/dist/cli-utils.d.ts.map +1 -0
  7. package/dist/cli-utils.js +64 -0
  8. package/dist/cli-utils.js.map +1 -0
  9. package/dist/errors.d.ts +52 -0
  10. package/dist/errors.d.ts.map +1 -0
  11. package/dist/errors.js +105 -0
  12. package/dist/errors.js.map +1 -0
  13. package/dist/index.d.ts +29 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +35 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/llm-provider.d.ts +29 -0
  18. package/dist/llm-provider.d.ts.map +1 -0
  19. package/dist/llm-provider.js +44 -0
  20. package/dist/llm-provider.js.map +1 -0
  21. package/dist/logger.d.ts +32 -0
  22. package/dist/logger.d.ts.map +1 -0
  23. package/dist/logger.js +75 -0
  24. package/dist/logger.js.map +1 -0
  25. package/dist/mock-llm.d.ts +70 -0
  26. package/dist/mock-llm.d.ts.map +1 -0
  27. package/dist/mock-llm.js +385 -0
  28. package/dist/mock-llm.js.map +1 -0
  29. package/dist/orchestrator-helpers.d.ts +20 -0
  30. package/dist/orchestrator-helpers.d.ts.map +1 -0
  31. package/dist/orchestrator-helpers.js +38 -0
  32. package/dist/orchestrator-helpers.js.map +1 -0
  33. package/dist/orchestrator.d.ts +124 -0
  34. package/dist/orchestrator.d.ts.map +1 -0
  35. package/dist/orchestrator.js +349 -0
  36. package/dist/orchestrator.js.map +1 -0
  37. package/dist/pipeline-registry.d.ts +120 -0
  38. package/dist/pipeline-registry.d.ts.map +1 -0
  39. package/dist/pipeline-registry.js +171 -0
  40. package/dist/pipeline-registry.js.map +1 -0
  41. package/dist/pipeline.d.ts +122 -0
  42. package/dist/pipeline.d.ts.map +1 -0
  43. package/dist/pipeline.js +152 -0
  44. package/dist/pipeline.js.map +1 -0
  45. package/dist/skill.d.ts +112 -0
  46. package/dist/skill.d.ts.map +1 -0
  47. package/dist/skill.js +12 -0
  48. package/dist/skill.js.map +1 -0
  49. package/dist/skills/io-skill.d.ts +49 -0
  50. package/dist/skills/io-skill.d.ts.map +1 -0
  51. package/dist/skills/io-skill.js +103 -0
  52. package/dist/skills/io-skill.js.map +1 -0
  53. package/dist/skills/llm-skill.d.ts +64 -0
  54. package/dist/skills/llm-skill.d.ts.map +1 -0
  55. package/dist/skills/llm-skill.js +112 -0
  56. package/dist/skills/llm-skill.js.map +1 -0
  57. package/dist/skills/transform-skill.d.ts +27 -0
  58. package/dist/skills/transform-skill.d.ts.map +1 -0
  59. package/dist/skills/transform-skill.js +32 -0
  60. package/dist/skills/transform-skill.js.map +1 -0
  61. package/dist/state/file-store.d.ts +25 -0
  62. package/dist/state/file-store.d.ts.map +1 -0
  63. package/dist/state/file-store.js +92 -0
  64. package/dist/state/file-store.js.map +1 -0
  65. package/dist/state/memory-store.d.ts +24 -0
  66. package/dist/state/memory-store.d.ts.map +1 -0
  67. package/dist/state/memory-store.js +65 -0
  68. package/dist/state/memory-store.js.map +1 -0
  69. package/dist/state/types.d.ts +40 -0
  70. package/dist/state/types.d.ts.map +1 -0
  71. package/dist/state/types.js +8 -0
  72. package/dist/state/types.js.map +1 -0
  73. package/dist/strategies/custom.d.ts +12 -0
  74. package/dist/strategies/custom.d.ts.map +1 -0
  75. package/dist/strategies/custom.js +14 -0
  76. package/dist/strategies/custom.js.map +1 -0
  77. package/dist/strategies/plan-and-execute.d.ts +27 -0
  78. package/dist/strategies/plan-and-execute.d.ts.map +1 -0
  79. package/dist/strategies/plan-and-execute.js +195 -0
  80. package/dist/strategies/plan-and-execute.js.map +1 -0
  81. package/dist/strategies/react.d.ts +27 -0
  82. package/dist/strategies/react.d.ts.map +1 -0
  83. package/dist/strategies/react.js +172 -0
  84. package/dist/strategies/react.js.map +1 -0
  85. package/dist/strategies/router.d.ts +11 -0
  86. package/dist/strategies/router.d.ts.map +1 -0
  87. package/dist/strategies/router.js +70 -0
  88. package/dist/strategies/router.js.map +1 -0
  89. package/dist/strategies/sequential.d.ts +12 -0
  90. package/dist/strategies/sequential.d.ts.map +1 -0
  91. package/dist/strategies/sequential.js +39 -0
  92. package/dist/strategies/sequential.js.map +1 -0
  93. package/dist/strategies/types.d.ts +62 -0
  94. package/dist/strategies/types.d.ts.map +1 -0
  95. package/dist/strategies/types.js +5 -0
  96. package/dist/strategies/types.js.map +1 -0
  97. package/dist/types.d.ts +83 -0
  98. package/dist/types.d.ts.map +1 -0
  99. package/dist/types.js +5 -0
  100. package/dist/types.js.map +1 -0
  101. package/package.json +28 -0
  102. package/src/agent.ts +243 -0
  103. package/src/cli-utils.ts +73 -0
  104. package/src/errors.ts +146 -0
  105. package/src/index.ts +124 -0
  106. package/src/llm-provider.ts +88 -0
  107. package/src/logger.ts +97 -0
  108. package/src/mock-llm.ts +433 -0
  109. package/src/orchestrator-helpers.ts +40 -0
  110. package/src/orchestrator.ts +522 -0
  111. package/src/pipeline-registry.ts +253 -0
  112. package/src/pipeline.ts +265 -0
  113. package/src/skill.ts +127 -0
  114. package/src/skills/io-skill.ts +133 -0
  115. package/src/skills/llm-skill.ts +207 -0
  116. package/src/skills/transform-skill.ts +61 -0
  117. package/src/state/file-store.ts +119 -0
  118. package/src/state/memory-store.ts +82 -0
  119. package/src/state/types.ts +53 -0
  120. package/src/strategies/custom.ts +24 -0
  121. package/src/strategies/plan-and-execute.ts +268 -0
  122. package/src/strategies/react.ts +239 -0
  123. package/src/strategies/router.ts +101 -0
  124. package/src/strategies/sequential.ts +55 -0
  125. package/src/strategies/types.ts +97 -0
  126. package/src/types.ts +102 -0
  127. package/tsconfig.json +9 -0
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Pipeline Registry — discover, load, and manage pipelines from various sources.
3
+ *
4
+ * This module provides:
5
+ * - PipelineSource: define where a pipeline comes from (npm, local path, git)
6
+ * - PipelineRegistry: local registry that maps names to sources
7
+ * - loadPipeline(): resolve a source to a BuiltPipeline
8
+ */
9
+
10
+ import { resolve } from 'path';
11
+ import { existsSync } from 'fs';
12
+ import { readFile } from 'fs/promises';
13
+ import type { BuiltPipeline } from './pipeline.js';
14
+ import type { ZodSchema } from 'zod';
15
+
16
+ // ── Pipeline Metadata ────────────────────────────────────────────────────────
17
+
18
+ /**
19
+ * Metadata for a pipeline package.
20
+ * Exported from the pipeline's main file.
21
+ */
22
+ export interface PipelinePackageMeta {
23
+ /** Unique name (e.g., 'content-generation') */
24
+ name: string;
25
+ /** Human-readable description */
26
+ description: string;
27
+ /** Semantic version */
28
+ version?: string;
29
+ /** Tags for discovery */
30
+ tags?: string[];
31
+ /** Author information */
32
+ author?: string;
33
+ /** Input schema (for display/documentation) */
34
+ inputSchema?: ZodSchema;
35
+ /** Output schema (for display/documentation) */
36
+ outputSchema?: ZodSchema;
37
+ }
38
+
39
+ /**
40
+ * Full pipeline export from a pipeline package.
41
+ */
42
+ export interface PipelinePackageExports {
43
+ /** The built pipeline */
44
+ pipeline: BuiltPipeline;
45
+ /** Optional metadata */
46
+ metadata?: PipelinePackageMeta;
47
+ /** Optional config schema for runtime validation */
48
+ configSchema?: ZodSchema;
49
+ /** Optional orchestrator override */
50
+ orchestrator?: unknown;
51
+ }
52
+
53
+ // ── Pipeline Source ─────────────────────────────────────────────────────────
54
+
55
+ /** Where a pipeline is loaded from */
56
+ export type PipelineSource =
57
+ | { type: 'npm'; package: string; export?: string }
58
+ | { type: 'path'; path: string }
59
+ | { type: 'git'; url: string; ref?: string };
60
+
61
+ /** Registered pipeline in the local registry */
62
+ export interface RegisteredPipeline {
63
+ /** Local name (e.g., 'my-pipeline') */
64
+ name: string;
65
+ /** Source where the pipeline is loaded from */
66
+ source: PipelineSource;
67
+ /** Optional version constraint (for npm) */
68
+ version?: string;
69
+ /** When this was registered */
70
+ registeredAt: Date;
71
+ /** Cached loaded exports */
72
+ exports?: PipelinePackageExports;
73
+ }
74
+
75
+ // ── Pipeline Registry ────────────────────────────────────────────────────────
76
+
77
+ /**
78
+ * Local pipeline registry.
79
+ * Maps friendly names to pipeline sources.
80
+ */
81
+ export class PipelineRegistry {
82
+ private readonly pipelines: Map<string, RegisteredPipeline> = new Map();
83
+ private readonly pipelineDirs: string[] = [];
84
+
85
+ constructor(pipelineDirs: string[] = []) {
86
+ this.pipelineDirs = pipelineDirs;
87
+ }
88
+
89
+ /**
90
+ * Register a pipeline from an npm package.
91
+ */
92
+ register(name: string, packageName: string, options?: { version?: string }): void {
93
+ this.pipelines.set(name, {
94
+ name,
95
+ source: { type: 'npm', package: packageName },
96
+ version: options?.version,
97
+ registeredAt: new Date(),
98
+ });
99
+ }
100
+
101
+ /**
102
+ * Register a pipeline from a local path.
103
+ */
104
+ registerPath(name: string, path: string): void {
105
+ this.pipelines.set(name, {
106
+ name,
107
+ source: { type: 'path', path: resolve(path) },
108
+ registeredAt: new Date(),
109
+ });
110
+ }
111
+
112
+ /**
113
+ * Register a pipeline from a git repository.
114
+ */
115
+ registerGit(name: string, url: string, ref?: string): void {
116
+ this.pipelines.set(name, {
117
+ name,
118
+ source: { type: 'git', url, ref },
119
+ registeredAt: new Date(),
120
+ });
121
+ }
122
+
123
+ /**
124
+ * Get a registered pipeline by name.
125
+ */
126
+ get(name: string): RegisteredPipeline | undefined {
127
+ return this.pipelines.get(name);
128
+ }
129
+
130
+ /**
131
+ * List all registered pipelines.
132
+ */
133
+ list(): RegisteredPipeline[] {
134
+ return Array.from(this.pipelines.values());
135
+ }
136
+
137
+ /**
138
+ * Remove a pipeline from the registry.
139
+ */
140
+ unregister(name: string): boolean {
141
+ return this.pipelines.delete(name);
142
+ }
143
+
144
+ /**
145
+ * Clear all registrations.
146
+ */
147
+ clear(): void {
148
+ this.pipelines.clear();
149
+ }
150
+ }
151
+
152
+ // ── Pipeline Loader ─────────────────────────────────────────────────────────
153
+
154
+ /**
155
+ * Load a pipeline from a source.
156
+ */
157
+ export async function loadPipeline(source: PipelineSource): Promise<PipelinePackageExports> {
158
+ switch (source.type) {
159
+ case 'path':
160
+ return loadFromPath(source.path);
161
+ case 'npm':
162
+ return loadFromNpm(source.package, source.export);
163
+ case 'git':
164
+ // TODO: Implement git loading (clone + package.json lookup)
165
+ throw new Error('Git pipeline loading not yet implemented');
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Load a pipeline from a local path.
171
+ */
172
+ async function loadFromPath(absolutePath: string): Promise<PipelinePackageExports> {
173
+ // Check if it's a directory (pipeline package) or a file
174
+ const dirExists = existsSync(absolutePath);
175
+ const fileExists = existsSync(absolutePath + '.ts') || existsSync(absolutePath + '.js');
176
+
177
+ if (dirExists) {
178
+ // It's a directory — look for pipeline.ts or package.json
179
+ const pkgPath = resolve(absolutePath, 'package.json');
180
+ if (existsSync(pkgPath)) {
181
+ const pkg = JSON.parse(await readFile(pkgPath, 'utf-8'));
182
+ const mainFile = resolve(absolutePath, pkg.main || pkg.exports?.['.'] || 'index.js');
183
+ return import(mainFile) as Promise<PipelinePackageExports>;
184
+ }
185
+ const pipelinePath = resolve(absolutePath, 'pipeline.ts');
186
+ if (existsSync(pipelinePath)) {
187
+ return import(pipelinePath) as Promise<PipelinePackageExports>;
188
+ }
189
+ throw new Error(`No pipeline.ts or package.json found in ${absolutePath}`);
190
+ }
191
+
192
+ if (fileExists) {
193
+ // Direct file import
194
+ const ext = existsSync(absolutePath + '.ts') ? '.ts' : '.js';
195
+ return import(absolutePath + ext) as Promise<PipelinePackageExports>;
196
+ }
197
+
198
+ throw new Error(`Pipeline not found at ${absolutePath}`);
199
+ }
200
+
201
+ /**
202
+ * Load a pipeline from an npm package.
203
+ * Note: This requires the package to be installed in node_modules.
204
+ */
205
+ async function loadFromNpm(packageName: string, exportPath?: string): Promise<PipelinePackageExports> {
206
+ try {
207
+ const resolved = exportPath
208
+ ? await import(`${packageName}/${exportPath}`)
209
+ : await import(packageName);
210
+
211
+ if (!resolved.pipeline) {
212
+ throw new Error(`Package ${packageName} does not export a 'pipeline'`);
213
+ }
214
+
215
+ return resolved as PipelinePackageExports;
216
+ } catch (err) {
217
+ if (err instanceof Error && err.message.includes('package')) {
218
+ throw new Error(`Pipeline package not found: ${packageName}. Did you install it?`);
219
+ }
220
+ throw err;
221
+ }
222
+ }
223
+
224
+ // ── Registry Resolution ────────────────────────────────────────────────────────
225
+
226
+ /**
227
+ * Create a registry from a config object.
228
+ */
229
+ export function createRegistryFromConfig(
230
+ config: Record<string, string | { package: string; version?: string }>,
231
+ ): PipelineRegistry {
232
+ const registry = new PipelineRegistry();
233
+
234
+ for (const [name, source] of Object.entries(config)) {
235
+ if (typeof source === 'string') {
236
+ // Short form: 'my-pipeline': './path' or 'my-pipeline': '@org/pkg'
237
+ if (source.startsWith('./') || source.startsWith('/')) {
238
+ registry.registerPath(name, source);
239
+ } else if (source.startsWith('git+') || source.startsWith('https://')) {
240
+ const url = source.startsWith('git+') ? source : `git+${source}`;
241
+ registry.registerGit(name, url);
242
+ } else {
243
+ // Assume npm package
244
+ registry.register(name, source);
245
+ }
246
+ } else {
247
+ // Long form: 'my-pipeline': { package: '@org/pkg', version: '1.0.0' }
248
+ registry.register(name, source.package, { version: source.version });
249
+ }
250
+ }
251
+
252
+ return registry;
253
+ }
@@ -0,0 +1,265 @@
1
+ /**
2
+ * Pipeline DSL — fluent builder for defining execution graphs.
3
+ *
4
+ * A Pipeline is a directed sequence of Steps. Each Step is a Skill, Agent,
5
+ * or another Pipeline. Built pipelines are immutable value objects executed
6
+ * by the Orchestrator.
7
+ */
8
+
9
+ import type { ZodSchema } from 'zod';
10
+ import type { Skill } from './skill.js';
11
+
12
+ // ── Step Types ────────────────────────────────────────────────────────────────
13
+
14
+ export type StepKind = 'skill' | 'map' | 'filter' | 'reduce' | 'branch' | 'parallel';
15
+
16
+ /** A single resolved step in the pipeline graph. */
17
+ export interface PipelineStep {
18
+ name: string;
19
+ kind: StepKind;
20
+ /** For 'skill' steps: the skill to run. */
21
+ skill?: Skill;
22
+ /** For 'map' | 'filter' | 'reduce': source field path (e.g. 'parse.services'). */
23
+ sourceField?: string;
24
+ /** For 'map': the sub-pipeline to run for each item. */
25
+ subPipeline?: BuiltPipeline;
26
+ /** For 'filter': predicate function. */
27
+ predicate?: (item: unknown, index: number) => boolean;
28
+ /** For 'reduce': reducer function. */
29
+ reducer?: (acc: unknown, item: unknown, index: number) => unknown;
30
+ /** For 'reduce': initial accumulator. */
31
+ initialValue?: unknown;
32
+ /** For 'parallel': array of sub-pipelines to run concurrently. */
33
+ branches?: BuiltPipeline[];
34
+ /** Concurrency limit for 'map' steps (default 1 = sequential). */
35
+ concurrency?: number;
36
+ /** How to handle item errors in 'map' steps. */
37
+ onItemError?: 'fail' | 'skip' | 'retry';
38
+ /** Max retries for items in 'map' with onItemError='retry'. */
39
+ maxItemRetries?: number;
40
+ /**
41
+ * Custom input mapper. Receives the current pipeline context and
42
+ * returns the input for this step. Default: pass entire previous output.
43
+ */
44
+ inputMapper?: (ctx: StepInputContext) => unknown;
45
+ /** Skip this step if this returns true. */
46
+ skipIf?: (ctx: StepInputContext) => boolean;
47
+ }
48
+
49
+ export interface StepInputContext {
50
+ /** The overall pipeline input. */
51
+ pipelineInput: unknown;
52
+ /** Outputs from all previously completed steps, keyed by step name. */
53
+ stepOutputs: Record<string, unknown>;
54
+ /** The output of the immediately preceding step. */
55
+ previousOutput: unknown;
56
+ }
57
+
58
+ // ── Built Pipeline (immutable) ────────────────────────────────────────────────
59
+
60
+ export interface BuiltPipeline {
61
+ name: string;
62
+ steps: PipelineStep[];
63
+ inputSchema?: ZodSchema;
64
+ outputSchema?: ZodSchema;
65
+ /** Whether to save step checkpoints for resume capability. */
66
+ checkpointing?: boolean;
67
+ }
68
+
69
+ // ── Pipeline Builder ──────────────────────────────────────────────────────────
70
+
71
+ export class PipelineBuilder {
72
+ private readonly _name: string;
73
+ private _steps: PipelineStep[] = [];
74
+ private _inputSchema?: ZodSchema;
75
+ private _outputSchema?: ZodSchema;
76
+ private _checkpointing = false;
77
+
78
+ constructor(name: string) {
79
+ this._name = name;
80
+ }
81
+
82
+ /** Declare the input schema for validation and documentation. */
83
+ input(schema: ZodSchema): this {
84
+ this._inputSchema = schema;
85
+ return this;
86
+ }
87
+
88
+ /** Declare the output schema for validation and documentation. */
89
+ output(schema: ZodSchema): this {
90
+ this._outputSchema = schema;
91
+ return this;
92
+ }
93
+
94
+ /** Enable checkpoint saving for resume capability. */
95
+ withCheckpointing(): this {
96
+ this._checkpointing = true;
97
+ return this;
98
+ }
99
+
100
+ /**
101
+ * Add a skill step.
102
+ *
103
+ * @param name Step name (must be unique within the pipeline).
104
+ * @param skill The skill to execute.
105
+ * @param options Optional input mapper and skip condition.
106
+ */
107
+ step(
108
+ name: string,
109
+ skill: Skill,
110
+ options?: {
111
+ input?: (ctx: StepInputContext) => unknown;
112
+ skipIf?: (ctx: StepInputContext) => boolean;
113
+ },
114
+ ): this {
115
+ this._steps.push({
116
+ name,
117
+ kind: 'skill',
118
+ skill,
119
+ inputMapper: options?.input,
120
+ skipIf: options?.skipIf,
121
+ });
122
+ return this;
123
+ }
124
+
125
+ /**
126
+ * Map over an array field, running a sub-pipeline for each item.
127
+ *
128
+ * @param sourceField Dot-path to the array in step outputs (e.g. 'parse.services').
129
+ * Use '*' to use the entire previous step output as array.
130
+ * @param subPipeline Sub-pipeline (built or builder) to run per item.
131
+ * @param options Concurrency and error handling options.
132
+ */
133
+ mapOver(
134
+ sourceField: string,
135
+ subPipeline: BuiltPipeline | PipelineBuilder,
136
+ options?: {
137
+ concurrency?: number;
138
+ onItemError?: 'fail' | 'skip' | 'retry';
139
+ maxItemRetries?: number;
140
+ name?: string;
141
+ },
142
+ ): this {
143
+ const built =
144
+ subPipeline instanceof PipelineBuilder ? subPipeline.build() : subPipeline;
145
+ this._steps.push({
146
+ name: options?.name ?? `map:${sourceField}`,
147
+ kind: 'map',
148
+ sourceField,
149
+ subPipeline: built,
150
+ concurrency: options?.concurrency ?? 1,
151
+ onItemError: options?.onItemError ?? 'fail',
152
+ maxItemRetries: options?.maxItemRetries ?? 0,
153
+ });
154
+ return this;
155
+ }
156
+
157
+ /**
158
+ * Filter an array field, keeping only items where predicate returns true.
159
+ */
160
+ filter(
161
+ sourceField: string,
162
+ predicate: (item: unknown, index: number) => boolean,
163
+ name?: string,
164
+ ): this {
165
+ this._steps.push({
166
+ name: name ?? `filter:${sourceField}`,
167
+ kind: 'filter',
168
+ sourceField,
169
+ predicate,
170
+ });
171
+ return this;
172
+ }
173
+
174
+ /**
175
+ * Reduce an array field to a single value.
176
+ */
177
+ reduce(
178
+ sourceField: string,
179
+ reducer: (acc: unknown, item: unknown, index: number) => unknown,
180
+ initialValue: unknown,
181
+ name?: string,
182
+ ): this {
183
+ this._steps.push({
184
+ name: name ?? `reduce:${sourceField}`,
185
+ kind: 'reduce',
186
+ sourceField,
187
+ reducer,
188
+ initialValue,
189
+ });
190
+ return this;
191
+ }
192
+
193
+ /**
194
+ * Run multiple sub-pipelines in parallel, collecting all results.
195
+ */
196
+ parallel(
197
+ branches: Array<BuiltPipeline | PipelineBuilder>,
198
+ name?: string,
199
+ ): this {
200
+ this._steps.push({
201
+ name: name ?? 'parallel',
202
+ kind: 'parallel',
203
+ branches: branches.map((b) =>
204
+ b instanceof PipelineBuilder ? b.build() : b,
205
+ ),
206
+ });
207
+ return this;
208
+ }
209
+
210
+ /**
211
+ * Conditionally branch: run one of several sub-pipelines based on a condition.
212
+ */
213
+ branch(
214
+ name: string,
215
+ condition: (ctx: StepInputContext) => string, // returns branch name
216
+ branches: Record<string, BuiltPipeline | PipelineBuilder>,
217
+ ): this {
218
+ // Implemented as a 'branch' step; orchestrator resolves at runtime
219
+ this._steps.push({
220
+ name,
221
+ kind: 'branch',
222
+ // Store condition and branches in inputMapper/predicate slots
223
+ // (Orchestrator handles branch resolution specially)
224
+ inputMapper: condition as unknown as (ctx: StepInputContext) => unknown,
225
+ branches: Object.values(branches).map((b) =>
226
+ b instanceof PipelineBuilder ? b.build() : b,
227
+ ),
228
+ });
229
+ return this;
230
+ }
231
+
232
+ /** Finalize and return the immutable pipeline definition. */
233
+ build(): BuiltPipeline {
234
+ return {
235
+ name: this._name,
236
+ steps: [...this._steps],
237
+ inputSchema: this._inputSchema,
238
+ outputSchema: this._outputSchema,
239
+ checkpointing: this._checkpointing,
240
+ };
241
+ }
242
+ }
243
+
244
+ // ── Public Factory ────────────────────────────────────────────────────────────
245
+
246
+ export const Pipeline = {
247
+ /** Create a new pipeline builder. */
248
+ create: (name: string) => new PipelineBuilder(name),
249
+ };
250
+
251
+ // ── Utility: resolve a dot-path from step outputs ─────────────────────────────
252
+
253
+ export function resolvePath(
254
+ stepOutputs: Record<string, unknown>,
255
+ path: string,
256
+ ): unknown {
257
+ if (path === '*') return stepOutputs;
258
+ const parts = path.split('.');
259
+ let current: unknown = stepOutputs;
260
+ for (const part of parts) {
261
+ if (current === null || current === undefined) return undefined;
262
+ current = (current as Record<string, unknown>)[part];
263
+ }
264
+ return current;
265
+ }
package/src/skill.ts ADDED
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Skill — the atomic unit of work in flomatai.
3
+ *
4
+ * A Skill is a typed, composable function that can:
5
+ * - Call an LLM (LLMSkill)
6
+ * - Transform data (TransformSkill)
7
+ * - Perform IO (IOSkill)
8
+ * - Execute Python (PythonSkill via bridge)
9
+ * - Wrap a sub-pipeline (CompositeSkill)
10
+ */
11
+
12
+ import type { ZodSchema } from 'zod';
13
+ import type { LLMProvider } from './llm-provider.js';
14
+ import type { StateStore } from './state/types.js';
15
+ import type { Logger } from './logger.js';
16
+
17
+ // ── MCP Client (minimal interface, keeps core provider-agnostic) ──────────────
18
+
19
+ /**
20
+ * Minimal interface that any MCP client must satisfy.
21
+ * Defined in core so OrchestratorConfig and SkillContext can reference it
22
+ * without hard-depending on @flomatai/mcp-client.
23
+ *
24
+ * @flomatai/mcp-client's MCPClient class implements this interface.
25
+ */
26
+ export interface MCPClientLike {
27
+ /**
28
+ * List all tools exposed by the connected MCP server.
29
+ * Returns an array of tool descriptors (name, description, inputSchema).
30
+ */
31
+ listTools(): Promise<Array<{
32
+ name: string;
33
+ description: string;
34
+ inputSchema: Record<string, unknown>;
35
+ }>>;
36
+ /**
37
+ * Call a named tool on the connected MCP server.
38
+ * @param name Tool name as returned by listTools().
39
+ * @param args Arguments matching the tool's inputSchema.
40
+ */
41
+ callTool(name: string, args: Record<string, unknown>): Promise<unknown>;
42
+ }
43
+
44
+ // ── Skill Metadata ────────────────────────────────────────────────────────────
45
+
46
+ export interface SkillMeta {
47
+ /** Unique identifier for this skill. Used in pipeline step names. */
48
+ name: string;
49
+ /** Human-readable description for agent routing and planning prompts. */
50
+ description: string;
51
+ /** Semantic version. */
52
+ version?: string;
53
+ /** Categorization tags (e.g. 'llm', 'transform', 'io'). */
54
+ tags?: string[];
55
+ /**
56
+ * Retry configuration.
57
+ * Defaults to 0 retries (fail immediately).
58
+ */
59
+ retries?: number;
60
+ /**
61
+ * Per-execution timeout in milliseconds.
62
+ * 0 or undefined means no timeout.
63
+ */
64
+ timeout?: number;
65
+ /**
66
+ * Cache configuration. If provided, identical inputs return cached results.
67
+ */
68
+ cache?: {
69
+ /** Time-to-live in seconds. */
70
+ ttl: number;
71
+ /** Function that derives a cache key from the input. */
72
+ key: (input: unknown) => string;
73
+ };
74
+ }
75
+
76
+ // ── Skill Context ─────────────────────────────────────────────────────────────
77
+
78
+ export interface SkillContext {
79
+ /** The LLM bound to this execution context. */
80
+ llm: LLMProvider;
81
+ /** Child logger scoped to this skill execution. */
82
+ logger: Logger;
83
+ /** State store for caching and cross-step communication. */
84
+ state: StateStore;
85
+ /** Emit a named event (for observability / side effects). */
86
+ emit: (event: string, data: unknown) => void;
87
+ /** Arbitrary config passed from the orchestrator. */
88
+ config: Record<string, unknown>;
89
+ /** Abort signal — check this for graceful cancellation. */
90
+ abortSignal: AbortSignal;
91
+ /** ID of the current run. */
92
+ runId: string;
93
+ /** LLM registry — retrieve a named LLM by key. */
94
+ getLLM: (key: string) => LLMProvider;
95
+ /**
96
+ * MCP client registry — retrieve a named MCP client by key.
97
+ * Only present when the Orchestrator is configured with an `mcp` registry.
98
+ */
99
+ getMCPClient?: (key: string) => MCPClientLike;
100
+ }
101
+
102
+ // ── Skill Interface ───────────────────────────────────────────────────────────
103
+
104
+ export interface Skill<TInput = unknown, TOutput = unknown> {
105
+ /** Metadata describing this skill. */
106
+ meta: SkillMeta;
107
+ /** Zod schema for validating input at runtime. */
108
+ inputSchema: ZodSchema<TInput>;
109
+ /** Zod schema for validating output at runtime. */
110
+ outputSchema: ZodSchema<TOutput>;
111
+ /**
112
+ * Execute the skill.
113
+ * @param input Validated input data.
114
+ * @param ctx Execution context (LLM, logger, state, etc.).
115
+ * @returns Validated output data.
116
+ */
117
+ execute(input: TInput, ctx: SkillContext): Promise<TOutput>;
118
+ }
119
+
120
+ // ── Skill Result (with metadata) ─────────────────────────────────────────────
121
+
122
+ export interface SkillResult<T = unknown> {
123
+ output: T;
124
+ tokensUsed: number;
125
+ durationMs: number;
126
+ cached: boolean;
127
+ }