@q1k-oss/btree-workflows 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/.claude/settings.local.json +31 -0
  2. package/CLAUDE.md +181 -0
  3. package/LICENSE +21 -0
  4. package/README.md +920 -0
  5. package/behaviour-tree-workflows-landing/index.html +16 -0
  6. package/behaviour-tree-workflows-landing/package-lock.json +2074 -0
  7. package/behaviour-tree-workflows-landing/package.json +31 -0
  8. package/behaviour-tree-workflows-landing/public/favicon.svg +17 -0
  9. package/behaviour-tree-workflows-landing/src/App.css +103 -0
  10. package/behaviour-tree-workflows-landing/src/App.tsx +176 -0
  11. package/behaviour-tree-workflows-landing/src/components/BlackboardInspector.css +89 -0
  12. package/behaviour-tree-workflows-landing/src/components/BlackboardInspector.tsx +64 -0
  13. package/behaviour-tree-workflows-landing/src/components/ExampleSelector.css +64 -0
  14. package/behaviour-tree-workflows-landing/src/components/ExampleSelector.tsx +34 -0
  15. package/behaviour-tree-workflows-landing/src/components/ExecutionLog.css +107 -0
  16. package/behaviour-tree-workflows-landing/src/components/ExecutionLog.tsx +85 -0
  17. package/behaviour-tree-workflows-landing/src/components/Header.css +50 -0
  18. package/behaviour-tree-workflows-landing/src/components/Header.tsx +26 -0
  19. package/behaviour-tree-workflows-landing/src/components/StatusBadge.css +45 -0
  20. package/behaviour-tree-workflows-landing/src/components/StatusBadge.tsx +15 -0
  21. package/behaviour-tree-workflows-landing/src/components/Toolbar.css +74 -0
  22. package/behaviour-tree-workflows-landing/src/components/Toolbar.tsx +53 -0
  23. package/behaviour-tree-workflows-landing/src/components/TreeVisualizer.css +67 -0
  24. package/behaviour-tree-workflows-landing/src/components/TreeVisualizer.tsx +192 -0
  25. package/behaviour-tree-workflows-landing/src/components/YamlEditor.css +18 -0
  26. package/behaviour-tree-workflows-landing/src/components/YamlEditor.tsx +96 -0
  27. package/behaviour-tree-workflows-landing/src/lib/count-nodes.ts +11 -0
  28. package/behaviour-tree-workflows-landing/src/lib/execution-engine.ts +96 -0
  29. package/behaviour-tree-workflows-landing/src/lib/tree-layout.ts +136 -0
  30. package/behaviour-tree-workflows-landing/src/lib/yaml-examples.ts +549 -0
  31. package/behaviour-tree-workflows-landing/src/main.tsx +9 -0
  32. package/behaviour-tree-workflows-landing/src/stubs/activepieces.ts +18 -0
  33. package/behaviour-tree-workflows-landing/src/stubs/fs.ts +24 -0
  34. package/behaviour-tree-workflows-landing/src/stubs/path.ts +16 -0
  35. package/behaviour-tree-workflows-landing/src/stubs/temporal-activity.ts +6 -0
  36. package/behaviour-tree-workflows-landing/src/stubs/temporal-workflow.ts +22 -0
  37. package/behaviour-tree-workflows-landing/tsconfig.json +25 -0
  38. package/behaviour-tree-workflows-landing/vite.config.ts +40 -0
  39. package/demo-google-sheets.ts +181 -0
  40. package/demo-runtime-variables.ts +174 -0
  41. package/demo-template.ts +208 -0
  42. package/docs/ARCHITECTURE_SUMMARY.md +613 -0
  43. package/docs/NODE_REFERENCE.md +504 -0
  44. package/docs/README.md +53 -0
  45. package/docs/custom-nodes-architecture.md +826 -0
  46. package/docs/observability.md +175 -0
  47. package/docs/yaml-specification.md +990 -0
  48. package/examples/temporal/README.md +117 -0
  49. package/examples/temporal/activities.ts +373 -0
  50. package/examples/temporal/client.ts +115 -0
  51. package/examples/temporal/python-worker/activities.py +339 -0
  52. package/examples/temporal/python-worker/requirements.txt +12 -0
  53. package/examples/temporal/python-worker/worker.py +106 -0
  54. package/examples/temporal/worker.ts +66 -0
  55. package/examples/temporal/workflows.ts +6 -0
  56. package/examples/temporal/yaml-workflow-loader.ts +105 -0
  57. package/examples/yaml-test.ts +97 -0
  58. package/examples/yaml-workflows/01-simple-sequence.yaml +25 -0
  59. package/examples/yaml-workflows/02-parallel-timeout.yaml +45 -0
  60. package/examples/yaml-workflows/03-ecommerce-checkout.yaml +94 -0
  61. package/examples/yaml-workflows/04-ai-agent-workflow.yaml +346 -0
  62. package/examples/yaml-workflows/05-order-processing.yaml +146 -0
  63. package/examples/yaml-workflows/06-activity-test.yaml +71 -0
  64. package/examples/yaml-workflows/07-activity-simple-test.yaml +43 -0
  65. package/examples/yaml-workflows/08-file-processing.yaml +141 -0
  66. package/examples/yaml-workflows/09-http-request.yaml +137 -0
  67. package/examples/yaml-workflows/README.md +211 -0
  68. package/package.json +38 -0
  69. package/src/actions/code-execution.schema.ts +27 -0
  70. package/src/actions/code-execution.ts +218 -0
  71. package/src/actions/generate-file.test.ts +516 -0
  72. package/src/actions/generate-file.ts +166 -0
  73. package/src/actions/http-request.test.ts +784 -0
  74. package/src/actions/http-request.ts +228 -0
  75. package/src/actions/index.ts +20 -0
  76. package/src/actions/parse-file.test.ts +448 -0
  77. package/src/actions/parse-file.ts +139 -0
  78. package/src/actions/python-script.test.ts +439 -0
  79. package/src/actions/python-script.ts +154 -0
  80. package/src/base-node.test.ts +511 -0
  81. package/src/base-node.ts +605 -0
  82. package/src/behavior-tree.test.ts +431 -0
  83. package/src/behavior-tree.ts +283 -0
  84. package/src/blackboard.test.ts +222 -0
  85. package/src/blackboard.ts +192 -0
  86. package/src/composites/conditional.schema.ts +19 -0
  87. package/src/composites/conditional.test.ts +309 -0
  88. package/src/composites/conditional.ts +129 -0
  89. package/src/composites/for-each.schema.ts +23 -0
  90. package/src/composites/for-each.test.ts +254 -0
  91. package/src/composites/for-each.ts +132 -0
  92. package/src/composites/index.ts +15 -0
  93. package/src/composites/memory-sequence.schema.ts +19 -0
  94. package/src/composites/memory-sequence.test.ts +223 -0
  95. package/src/composites/memory-sequence.ts +98 -0
  96. package/src/composites/parallel.schema.ts +28 -0
  97. package/src/composites/parallel.test.ts +502 -0
  98. package/src/composites/parallel.ts +157 -0
  99. package/src/composites/reactive-sequence.schema.ts +19 -0
  100. package/src/composites/reactive-sequence.test.ts +170 -0
  101. package/src/composites/reactive-sequence.ts +85 -0
  102. package/src/composites/recovery.schema.ts +19 -0
  103. package/src/composites/recovery.test.ts +366 -0
  104. package/src/composites/recovery.ts +90 -0
  105. package/src/composites/selector.schema.ts +19 -0
  106. package/src/composites/selector.test.ts +387 -0
  107. package/src/composites/selector.ts +85 -0
  108. package/src/composites/sequence.schema.ts +19 -0
  109. package/src/composites/sequence.test.ts +337 -0
  110. package/src/composites/sequence.ts +72 -0
  111. package/src/composites/sub-tree.schema.ts +21 -0
  112. package/src/composites/sub-tree.test.ts +893 -0
  113. package/src/composites/sub-tree.ts +177 -0
  114. package/src/composites/while.schema.ts +24 -0
  115. package/src/composites/while.test.ts +381 -0
  116. package/src/composites/while.ts +149 -0
  117. package/src/data-store/index.ts +10 -0
  118. package/src/data-store/memory-store.ts +161 -0
  119. package/src/data-store/types.ts +94 -0
  120. package/src/debug/breakpoint.test.ts +47 -0
  121. package/src/debug/breakpoint.ts +30 -0
  122. package/src/debug/index.ts +17 -0
  123. package/src/debug/resume-point.test.ts +49 -0
  124. package/src/debug/resume-point.ts +29 -0
  125. package/src/decorators/delay.schema.ts +21 -0
  126. package/src/decorators/delay.test.ts +261 -0
  127. package/src/decorators/delay.ts +140 -0
  128. package/src/decorators/force-result.schema.ts +32 -0
  129. package/src/decorators/force-result.test.ts +133 -0
  130. package/src/decorators/force-result.ts +63 -0
  131. package/src/decorators/index.ts +13 -0
  132. package/src/decorators/invert.schema.ts +19 -0
  133. package/src/decorators/invert.test.ts +135 -0
  134. package/src/decorators/invert.ts +42 -0
  135. package/src/decorators/keep-running.schema.ts +20 -0
  136. package/src/decorators/keep-running.test.ts +105 -0
  137. package/src/decorators/keep-running.ts +49 -0
  138. package/src/decorators/precondition.schema.ts +19 -0
  139. package/src/decorators/precondition.test.ts +351 -0
  140. package/src/decorators/precondition.ts +139 -0
  141. package/src/decorators/repeat.schema.ts +21 -0
  142. package/src/decorators/repeat.test.ts +187 -0
  143. package/src/decorators/repeat.ts +94 -0
  144. package/src/decorators/run-once.schema.ts +19 -0
  145. package/src/decorators/run-once.test.ts +140 -0
  146. package/src/decorators/run-once.ts +61 -0
  147. package/src/decorators/soft-assert.schema.ts +19 -0
  148. package/src/decorators/soft-assert.test.ts +107 -0
  149. package/src/decorators/soft-assert.ts +68 -0
  150. package/src/decorators/timeout.schema.ts +21 -0
  151. package/src/decorators/timeout.test.ts +274 -0
  152. package/src/decorators/timeout.ts +159 -0
  153. package/src/errors.test.ts +63 -0
  154. package/src/errors.ts +34 -0
  155. package/src/events.test.ts +347 -0
  156. package/src/events.ts +183 -0
  157. package/src/index.ts +80 -0
  158. package/src/integrations/index.ts +30 -0
  159. package/src/integrations/integration-action.test.ts +571 -0
  160. package/src/integrations/integration-action.ts +233 -0
  161. package/src/integrations/piece-executor.ts +320 -0
  162. package/src/observability/execution-tracker.ts +320 -0
  163. package/src/observability/index.ts +23 -0
  164. package/src/observability/sinks.ts +138 -0
  165. package/src/observability/types.ts +130 -0
  166. package/src/registry-utils.ts +147 -0
  167. package/src/registry.test.ts +466 -0
  168. package/src/registry.ts +334 -0
  169. package/src/schemas/base.schema.ts +104 -0
  170. package/src/schemas/index.ts +223 -0
  171. package/src/schemas/integration.test.ts +238 -0
  172. package/src/schemas/tree-definition.schema.ts +170 -0
  173. package/src/schemas/validation.test.ts +146 -0
  174. package/src/schemas/validation.ts +122 -0
  175. package/src/scripting/index.ts +22 -0
  176. package/src/templates/template-loader.test.ts +281 -0
  177. package/src/templates/template-loader.ts +152 -0
  178. package/src/temporal-integration.test.ts +213 -0
  179. package/src/test-nodes.ts +259 -0
  180. package/src/types.ts +503 -0
  181. package/src/utilities/index.ts +17 -0
  182. package/src/utilities/log-message.test.ts +275 -0
  183. package/src/utilities/log-message.ts +134 -0
  184. package/src/utilities/regex-extract.test.ts +138 -0
  185. package/src/utilities/regex-extract.ts +108 -0
  186. package/src/utilities/variable-resolver.test.ts +416 -0
  187. package/src/utilities/variable-resolver.ts +318 -0
  188. package/src/utils/error-handler.test.ts +117 -0
  189. package/src/utils/error-handler.ts +48 -0
  190. package/src/utils/signal-check.test.ts +234 -0
  191. package/src/utils/signal-check.ts +140 -0
  192. package/src/yaml/errors.ts +143 -0
  193. package/src/yaml/index.ts +30 -0
  194. package/src/yaml/loader.ts +39 -0
  195. package/src/yaml/parser.ts +286 -0
  196. package/src/yaml/validation/semantic-validator.ts +196 -0
  197. package/templates/google-sheets/insert-row.yaml +76 -0
  198. package/templates/notification-sender.yaml +33 -0
  199. package/templates/order-validation.yaml +44 -0
  200. package/tsconfig.json +24 -0
  201. package/vitest.config.ts +25 -0
  202. package/workflows/order-processor.yaml +59 -0
  203. package/workflows/process-order-workflow.yaml +142 -0
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Integration Action Node
3
+ * Execute third-party service actions via Active Pieces packages
4
+ *
5
+ * Features:
6
+ * - Variable resolution: ${input.key}, ${bb.key}, ${env.KEY}, ${param.key}
7
+ * - Pluggable token provider for OAuth/API key authentication
8
+ * - Dynamic Active Pieces action execution
9
+ * - Result storage in blackboard
10
+ */
11
+
12
+ import { ActionNode } from "../base-node.js";
13
+ import { ConfigurationError } from "../errors.js";
14
+ import {
15
+ type TemporalContext,
16
+ type NodeConfiguration,
17
+ type PieceActivityRequest,
18
+ type PieceAuth,
19
+ type TokenProvider,
20
+ NodeStatus,
21
+ } from "../types.js";
22
+ import { executePieceAction } from "./piece-executor.js";
23
+ import { resolveValue, type VariableContext } from "../utilities/variable-resolver.js";
24
+
25
+ // Re-export TokenProvider for backward compatibility
26
+ export type { TokenProvider } from "../types.js";
27
+
28
+ /**
29
+ * Configuration for IntegrationAction node
30
+ */
31
+ export interface IntegrationActionConfig extends NodeConfiguration {
32
+ /** Provider name: 'google-sheets', 'slack', 'openai', etc. */
33
+ provider: string;
34
+ /** Action name from Active Pieces: 'append_row', 'send_message', etc. */
35
+ action: string;
36
+ /** Action inputs (supports ${input.key}, ${bb.key}, ${env.KEY}, ${param.key}) */
37
+ inputs?: Record<string, unknown>;
38
+ /** Connection ID to use (optional, defaults to user's primary connection) */
39
+ connectionId?: string;
40
+ /** Whether to store the result in blackboard (default: true) */
41
+ storeResult?: boolean;
42
+ /** Custom blackboard key for result (default: ${nodeId}.result) */
43
+ resultKey?: string;
44
+ }
45
+
46
+ /**
47
+ * Extended context with token provider
48
+ */
49
+ export interface IntegrationContext extends TemporalContext {
50
+ /** Token provider function for fetching OAuth tokens */
51
+ tokenProvider?: TokenProvider;
52
+ /** Tenant ID for multi-tenant token lookup */
53
+ tenantId?: string;
54
+ /** User ID for user-scoped token lookup */
55
+ userId?: string;
56
+ }
57
+
58
+ /**
59
+ * Integration Action Node
60
+ *
61
+ * Executes actions on third-party services using Active Pieces packages.
62
+ * Requires a token provider to be set in context for authentication.
63
+ *
64
+ * Supports variable resolution:
65
+ * - ${input.key} - Workflow input parameters
66
+ * - ${bb.key} - Blackboard values
67
+ * - ${env.KEY} - Environment variables
68
+ * - ${param.key} - Test data parameters
69
+ *
70
+ * @example
71
+ * ```yaml
72
+ * type: IntegrationAction
73
+ * id: add-to-sheet
74
+ * props:
75
+ * provider: google-sheets
76
+ * action: append_row
77
+ * inputs:
78
+ * spreadsheetId: "${input.spreadsheetId}"
79
+ * sheetName: "Orders"
80
+ * values:
81
+ * - "${input.orderId}"
82
+ * - "${bb.customerName}"
83
+ * - "${bb.total}"
84
+ * ```
85
+ */
86
+ export class IntegrationAction extends ActionNode {
87
+ private provider: string;
88
+ private action: string;
89
+ private inputs: Record<string, unknown>;
90
+ private connectionId?: string;
91
+ private storeResult: boolean;
92
+ private resultKey: string;
93
+
94
+ constructor(config: IntegrationActionConfig) {
95
+ super(config);
96
+
97
+ if (!config.provider) {
98
+ throw new ConfigurationError("IntegrationAction requires provider");
99
+ }
100
+
101
+ if (!config.action) {
102
+ throw new ConfigurationError("IntegrationAction requires action");
103
+ }
104
+
105
+ this.provider = config.provider;
106
+ this.action = config.action;
107
+ this.inputs = config.inputs || {};
108
+ this.connectionId = config.connectionId;
109
+ this.storeResult = config.storeResult !== false; // Default true
110
+ this.resultKey = config.resultKey || `${this.id}.result`;
111
+ }
112
+
113
+ protected async executeTick(context: TemporalContext): Promise<NodeStatus> {
114
+ const integrationContext = context as IntegrationContext;
115
+
116
+ // Validate token provider is available
117
+ if (!integrationContext.tokenProvider) {
118
+ this._lastError =
119
+ "No token provider configured in context. " +
120
+ "Set context.tokenProvider to fetch OAuth tokens.";
121
+ this.log(`Error: ${this._lastError}`);
122
+ return NodeStatus.FAILURE;
123
+ }
124
+
125
+ try {
126
+ // 1. Resolve inputs from blackboard
127
+ const resolvedInputs = this.resolveInputs(context);
128
+ this.log(`Resolved inputs: ${JSON.stringify(resolvedInputs)}`);
129
+
130
+ // 2. Get authentication via token provider
131
+ const auth = await integrationContext.tokenProvider(
132
+ context,
133
+ this.provider,
134
+ this.connectionId
135
+ );
136
+ this.log(`Got authentication for provider: ${this.provider}`);
137
+
138
+ // 3. Execute via activity (if available) or inline (standalone mode)
139
+ const result = await this.executeAction(context, resolvedInputs, auth);
140
+
141
+ // 4. Store result in blackboard if enabled
142
+ if (this.storeResult) {
143
+ context.blackboard.set(this.resultKey, result);
144
+ this.log(`Stored result in blackboard: ${this.resultKey}`);
145
+ }
146
+
147
+ this.log(
148
+ `Integration action completed: ${this.provider}/${this.action}`
149
+ );
150
+ return NodeStatus.SUCCESS;
151
+ } catch (error) {
152
+ this._lastError =
153
+ error instanceof Error ? error.message : String(error);
154
+ this.log(`Integration action failed: ${this._lastError}`);
155
+ return NodeStatus.FAILURE;
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Dual-mode execution:
161
+ * - Activity mode: Use context.activities (deterministic for Temporal)
162
+ * - Standalone mode: Inline executePieceAction (for testing)
163
+ */
164
+ private async executeAction(
165
+ context: TemporalContext,
166
+ inputs: Record<string, unknown>,
167
+ auth: PieceAuth
168
+ ): Promise<unknown> {
169
+ const request: PieceActivityRequest = {
170
+ provider: this.provider,
171
+ action: this.action,
172
+ inputs,
173
+ auth,
174
+ };
175
+
176
+ // Activity mode: deterministic execution
177
+ if (context.activities?.executePieceAction) {
178
+ this.log(`Executing via activity: ${this.provider}/${this.action}`);
179
+ return context.activities.executePieceAction(request);
180
+ }
181
+
182
+ // Standalone mode: inline execution (for testing without Temporal)
183
+ this.log(`Executing inline (standalone): ${this.provider}/${this.action}`);
184
+ return executePieceAction(request);
185
+ }
186
+
187
+ /**
188
+ * Resolve variable references in inputs
189
+ * Supports ${input.key}, ${bb.key}, ${env.KEY}, ${param.key}
190
+ */
191
+ private resolveInputs(context: TemporalContext): Record<string, unknown> {
192
+ const varCtx: VariableContext = {
193
+ blackboard: context.blackboard,
194
+ input: context.input,
195
+ testData: context.testData,
196
+ };
197
+ // Use preserveUndefined: false to return empty string/undefined for missing values
198
+ // This matches the original behavior
199
+ return resolveValue(this.inputs, varCtx, { preserveUndefined: false }) as Record<string, unknown>;
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Default token provider that reads from environment variables
205
+ * Useful for testing and simple deployments
206
+ *
207
+ * Looks for:
208
+ * - {PROVIDER}_ACCESS_TOKEN (e.g., GOOGLE_ACCESS_TOKEN)
209
+ * - {PROVIDER}_API_KEY (e.g., OPENAI_API_KEY)
210
+ */
211
+ export const envTokenProvider: TokenProvider = async (
212
+ _context,
213
+ provider
214
+ ): Promise<PieceAuth> => {
215
+ const normalizedProvider = provider.toUpperCase().replace(/-/g, "_");
216
+
217
+ // Try access token first
218
+ const accessToken = process.env[`${normalizedProvider}_ACCESS_TOKEN`];
219
+ if (accessToken) {
220
+ return { access_token: accessToken };
221
+ }
222
+
223
+ // Try API key
224
+ const apiKey = process.env[`${normalizedProvider}_API_KEY`];
225
+ if (apiKey) {
226
+ return { api_key: apiKey };
227
+ }
228
+
229
+ throw new Error(
230
+ `No token found for provider ${provider}. ` +
231
+ `Set ${normalizedProvider}_ACCESS_TOKEN or ${normalizedProvider}_API_KEY environment variable.`
232
+ );
233
+ };
@@ -0,0 +1,320 @@
1
+ /**
2
+ * Piece Executor
3
+ * Dynamically executes Active Pieces actions
4
+ *
5
+ * Active Pieces is an open-source automation platform with 440+ connectors.
6
+ * This module wraps their npm packages for use in btree workflows.
7
+ *
8
+ * @see https://activepieces.com/docs
9
+ */
10
+
11
+ import type { PieceActivityRequest, PieceAuth } from "../types.js";
12
+
13
+ // Re-export types for backward compatibility
14
+ export type { PieceAuth, PieceActivityRequest as PieceActionRequest } from "../types.js";
15
+
16
+ /**
17
+ * Mapping from provider names to Active Pieces package names
18
+ * Add entries as needed when integrating new providers
19
+ */
20
+ const PROVIDER_TO_PIECE: Record<string, string> = {
21
+ // Google services
22
+ "google": "google-sheets",
23
+ "google-sheets": "google-sheets",
24
+ "google-drive": "google-drive",
25
+ "google-calendar": "google-calendar",
26
+ "gmail": "gmail",
27
+
28
+ // Communication
29
+ "slack": "slack",
30
+ "discord": "discord",
31
+ "telegram": "telegram-bot",
32
+ "twilio": "twilio",
33
+
34
+ // AI/ML
35
+ "openai": "openai",
36
+ "anthropic": "anthropic",
37
+
38
+ // CRM & Sales
39
+ "hubspot": "hubspot",
40
+ "salesforce": "salesforce",
41
+
42
+ // Productivity
43
+ "notion": "notion",
44
+ "airtable": "airtable",
45
+ "asana": "asana",
46
+ "trello": "trello",
47
+
48
+ // Development
49
+ "github": "github",
50
+ "gitlab": "gitlab",
51
+
52
+ // E-commerce
53
+ "shopify": "shopify",
54
+ "stripe": "stripe",
55
+
56
+ // Other
57
+ "http": "http",
58
+ "webhook": "http",
59
+ };
60
+
61
+ /**
62
+ * Cache for loaded pieces to avoid repeated dynamic imports
63
+ */
64
+ const pieceCache: Map<string, unknown> = new Map();
65
+
66
+ /**
67
+ * Get the Active Pieces package name for a provider
68
+ */
69
+ function getPiecePackageName(provider: string): string {
70
+ const pieceName = PROVIDER_TO_PIECE[provider.toLowerCase()];
71
+ if (pieceName) {
72
+ return `@activepieces/piece-${pieceName}`;
73
+ }
74
+ // Default: assume package name matches provider name
75
+ return `@activepieces/piece-${provider.toLowerCase()}`;
76
+ }
77
+
78
+ /**
79
+ * Mapping from package name to piece export name
80
+ * Active Pieces packages export the piece with a specific name (e.g., googleSheets, slack)
81
+ */
82
+ const PIECE_EXPORT_NAMES: Record<string, string> = {
83
+ "@activepieces/piece-google-sheets": "googleSheets",
84
+ "@activepieces/piece-slack": "slack",
85
+ "@activepieces/piece-openai": "openai",
86
+ "@activepieces/piece-discord": "discord",
87
+ "@activepieces/piece-notion": "notion",
88
+ "@activepieces/piece-github": "github",
89
+ "@activepieces/piece-http": "http",
90
+ };
91
+
92
+ /**
93
+ * Dynamically load an Active Pieces package
94
+ */
95
+ async function loadPiece(packageName: string): Promise<unknown> {
96
+ // Check cache first
97
+ if (pieceCache.has(packageName)) {
98
+ return pieceCache.get(packageName);
99
+ }
100
+
101
+ try {
102
+ // Dynamic import of the piece package
103
+ const pieceModule = await import(packageName);
104
+
105
+ // Active Pieces exports vary - try to find the piece object
106
+ // Format 1: { googleSheets: { _actions: {...} } }
107
+ // Format 2: { default: { actions: {...} } }
108
+ let piece: unknown = pieceModule;
109
+
110
+ // Check for known export names
111
+ const exportName = PIECE_EXPORT_NAMES[packageName];
112
+ if (exportName && pieceModule[exportName]) {
113
+ piece = pieceModule[exportName];
114
+ } else {
115
+ // Try to find the piece by looking for an object with _actions
116
+ for (const key of Object.keys(pieceModule)) {
117
+ const exported = pieceModule[key];
118
+ if (exported && typeof exported === "object" && "_actions" in exported) {
119
+ piece = exported;
120
+ break;
121
+ }
122
+ }
123
+ }
124
+
125
+ pieceCache.set(packageName, piece);
126
+ return piece;
127
+ } catch (error) {
128
+ const message = error instanceof Error ? error.message : String(error);
129
+
130
+ // Provide helpful error message
131
+ if (message.includes("Cannot find module")) {
132
+ throw new Error(
133
+ `Active Pieces package '${packageName}' is not installed. ` +
134
+ `Run: npm install ${packageName}`
135
+ );
136
+ }
137
+
138
+ throw new Error(`Failed to load piece package '${packageName}': ${message}`);
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Execute an Active Pieces action
144
+ *
145
+ * @param request - Action request with provider, action, inputs, and auth
146
+ * @returns The result from the Active Pieces action
147
+ *
148
+ * @example
149
+ * ```typescript
150
+ * const result = await executePieceAction({
151
+ * provider: 'google-sheets',
152
+ * action: 'append_row',
153
+ * inputs: {
154
+ * spreadsheetId: 'xxx',
155
+ * sheetName: 'Sheet1',
156
+ * values: ['A', 'B', 'C']
157
+ * },
158
+ * auth: { access_token: 'ya29.xxx' }
159
+ * });
160
+ * ```
161
+ */
162
+ export async function executePieceAction(
163
+ request: PieceActivityRequest
164
+ ): Promise<unknown> {
165
+ const { provider, action, inputs, auth } = request;
166
+
167
+ // Get package name and load piece
168
+ const packageName = getPiecePackageName(provider);
169
+ const piece = (await loadPiece(packageName)) as Record<string, unknown>;
170
+
171
+ // Find the actions object
172
+ // Active Pieces uses _actions internally, but some may use actions
173
+ let actions: Record<string, unknown> | undefined;
174
+
175
+ // Try _actions first (Active Pieces internal format)
176
+ if (piece._actions && typeof piece._actions === "object") {
177
+ actions = piece._actions as Record<string, unknown>;
178
+ }
179
+ // Then try actions (public API format)
180
+ else if (piece.actions && typeof piece.actions === "object") {
181
+ actions = piece.actions as Record<string, unknown>;
182
+ }
183
+ // Try default export
184
+ else if (piece.default && typeof piece.default === "object") {
185
+ const defaultExport = piece.default as Record<string, unknown>;
186
+ actions = (defaultExport._actions || defaultExport.actions) as Record<string, unknown> | undefined;
187
+ }
188
+
189
+ if (!actions) {
190
+ throw new Error(
191
+ `Piece '${packageName}' does not export actions. ` +
192
+ `This may not be a valid Active Pieces package.`
193
+ );
194
+ }
195
+
196
+ // Find the specific action
197
+ const actionDef = actions[action] as Record<string, unknown> | undefined;
198
+
199
+ if (!actionDef) {
200
+ const availableActions = Object.keys(actions).join(", ");
201
+ throw new Error(
202
+ `Action '${action}' not found in piece '${packageName}'. ` +
203
+ `Available actions: ${availableActions}`
204
+ );
205
+ }
206
+
207
+ // Verify action has a run method
208
+ if (typeof actionDef.run !== "function") {
209
+ throw new Error(
210
+ `Action '${action}' in piece '${packageName}' does not have a run method`
211
+ );
212
+ }
213
+
214
+ // Execute the action
215
+ // Active Pieces actions expect: { auth, propsValue, ... }
216
+ const runFn = actionDef.run as (context: Record<string, unknown>) => Promise<unknown>;
217
+
218
+ const result = await runFn({
219
+ auth,
220
+ propsValue: inputs,
221
+ // Additional context that some pieces might need
222
+ store: createMockStore(),
223
+ files: createMockFiles(),
224
+ });
225
+
226
+ return result;
227
+ }
228
+
229
+ /**
230
+ * Create a mock store for pieces that use state storage
231
+ * In a real implementation, this could be backed by Redis or the blackboard
232
+ */
233
+ function createMockStore(): Record<string, unknown> {
234
+ const storage: Record<string, unknown> = {};
235
+
236
+ return {
237
+ async get(key: string): Promise<unknown> {
238
+ return storage[key];
239
+ },
240
+ async put(key: string, value: unknown): Promise<void> {
241
+ storage[key] = value;
242
+ },
243
+ async delete(key: string): Promise<void> {
244
+ delete storage[key];
245
+ },
246
+ };
247
+ }
248
+
249
+ /**
250
+ * Create a mock files helper for pieces that handle file uploads
251
+ */
252
+ function createMockFiles(): Record<string, unknown> {
253
+ return {
254
+ async write(params: { fileName: string; data: Buffer }): Promise<string> {
255
+ // In a real implementation, this would upload to a file storage
256
+ console.log(`[PieceExecutor] Mock file write: ${params.fileName}`);
257
+ return `mock://files/${params.fileName}`;
258
+ },
259
+ };
260
+ }
261
+
262
+ /**
263
+ * List available actions for a provider
264
+ * Useful for UI builders and documentation
265
+ */
266
+ export async function listPieceActions(
267
+ provider: string
268
+ ): Promise<{ name: string; displayName?: string; description?: string }[]> {
269
+ const packageName = getPiecePackageName(provider);
270
+ const piece = (await loadPiece(packageName)) as Record<string, unknown>;
271
+
272
+ let actions: Record<string, unknown> | undefined;
273
+
274
+ // Try _actions first (Active Pieces internal format)
275
+ if (piece._actions && typeof piece._actions === "object") {
276
+ actions = piece._actions as Record<string, unknown>;
277
+ }
278
+ // Then try actions (public API format)
279
+ else if (piece.actions && typeof piece.actions === "object") {
280
+ actions = piece.actions as Record<string, unknown>;
281
+ }
282
+ // Try default export
283
+ else if (piece.default && typeof piece.default === "object") {
284
+ const defaultExport = piece.default as Record<string, unknown>;
285
+ actions = (defaultExport._actions || defaultExport.actions) as Record<string, unknown> | undefined;
286
+ }
287
+
288
+ if (!actions) {
289
+ return [];
290
+ }
291
+
292
+ return Object.entries(actions).map(([name, def]) => {
293
+ const actionDef = def as Record<string, unknown>;
294
+ return {
295
+ name,
296
+ displayName: actionDef.displayName as string | undefined,
297
+ description: actionDef.description as string | undefined,
298
+ };
299
+ });
300
+ }
301
+
302
+ /**
303
+ * Check if a piece is installed
304
+ */
305
+ export async function isPieceInstalled(provider: string): Promise<boolean> {
306
+ const packageName = getPiecePackageName(provider);
307
+ try {
308
+ await loadPiece(packageName);
309
+ return true;
310
+ } catch {
311
+ return false;
312
+ }
313
+ }
314
+
315
+ /**
316
+ * Clear the piece cache (useful for testing)
317
+ */
318
+ export function clearPieceCache(): void {
319
+ pieceCache.clear();
320
+ }