@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.
- package/.claude/settings.local.json +31 -0
- package/CLAUDE.md +181 -0
- package/LICENSE +21 -0
- package/README.md +920 -0
- package/behaviour-tree-workflows-landing/index.html +16 -0
- package/behaviour-tree-workflows-landing/package-lock.json +2074 -0
- package/behaviour-tree-workflows-landing/package.json +31 -0
- package/behaviour-tree-workflows-landing/public/favicon.svg +17 -0
- package/behaviour-tree-workflows-landing/src/App.css +103 -0
- package/behaviour-tree-workflows-landing/src/App.tsx +176 -0
- package/behaviour-tree-workflows-landing/src/components/BlackboardInspector.css +89 -0
- package/behaviour-tree-workflows-landing/src/components/BlackboardInspector.tsx +64 -0
- package/behaviour-tree-workflows-landing/src/components/ExampleSelector.css +64 -0
- package/behaviour-tree-workflows-landing/src/components/ExampleSelector.tsx +34 -0
- package/behaviour-tree-workflows-landing/src/components/ExecutionLog.css +107 -0
- package/behaviour-tree-workflows-landing/src/components/ExecutionLog.tsx +85 -0
- package/behaviour-tree-workflows-landing/src/components/Header.css +50 -0
- package/behaviour-tree-workflows-landing/src/components/Header.tsx +26 -0
- package/behaviour-tree-workflows-landing/src/components/StatusBadge.css +45 -0
- package/behaviour-tree-workflows-landing/src/components/StatusBadge.tsx +15 -0
- package/behaviour-tree-workflows-landing/src/components/Toolbar.css +74 -0
- package/behaviour-tree-workflows-landing/src/components/Toolbar.tsx +53 -0
- package/behaviour-tree-workflows-landing/src/components/TreeVisualizer.css +67 -0
- package/behaviour-tree-workflows-landing/src/components/TreeVisualizer.tsx +192 -0
- package/behaviour-tree-workflows-landing/src/components/YamlEditor.css +18 -0
- package/behaviour-tree-workflows-landing/src/components/YamlEditor.tsx +96 -0
- package/behaviour-tree-workflows-landing/src/lib/count-nodes.ts +11 -0
- package/behaviour-tree-workflows-landing/src/lib/execution-engine.ts +96 -0
- package/behaviour-tree-workflows-landing/src/lib/tree-layout.ts +136 -0
- package/behaviour-tree-workflows-landing/src/lib/yaml-examples.ts +549 -0
- package/behaviour-tree-workflows-landing/src/main.tsx +9 -0
- package/behaviour-tree-workflows-landing/src/stubs/activepieces.ts +18 -0
- package/behaviour-tree-workflows-landing/src/stubs/fs.ts +24 -0
- package/behaviour-tree-workflows-landing/src/stubs/path.ts +16 -0
- package/behaviour-tree-workflows-landing/src/stubs/temporal-activity.ts +6 -0
- package/behaviour-tree-workflows-landing/src/stubs/temporal-workflow.ts +22 -0
- package/behaviour-tree-workflows-landing/tsconfig.json +25 -0
- package/behaviour-tree-workflows-landing/vite.config.ts +40 -0
- package/demo-google-sheets.ts +181 -0
- package/demo-runtime-variables.ts +174 -0
- package/demo-template.ts +208 -0
- package/docs/ARCHITECTURE_SUMMARY.md +613 -0
- package/docs/NODE_REFERENCE.md +504 -0
- package/docs/README.md +53 -0
- package/docs/custom-nodes-architecture.md +826 -0
- package/docs/observability.md +175 -0
- package/docs/yaml-specification.md +990 -0
- package/examples/temporal/README.md +117 -0
- package/examples/temporal/activities.ts +373 -0
- package/examples/temporal/client.ts +115 -0
- package/examples/temporal/python-worker/activities.py +339 -0
- package/examples/temporal/python-worker/requirements.txt +12 -0
- package/examples/temporal/python-worker/worker.py +106 -0
- package/examples/temporal/worker.ts +66 -0
- package/examples/temporal/workflows.ts +6 -0
- package/examples/temporal/yaml-workflow-loader.ts +105 -0
- package/examples/yaml-test.ts +97 -0
- package/examples/yaml-workflows/01-simple-sequence.yaml +25 -0
- package/examples/yaml-workflows/02-parallel-timeout.yaml +45 -0
- package/examples/yaml-workflows/03-ecommerce-checkout.yaml +94 -0
- package/examples/yaml-workflows/04-ai-agent-workflow.yaml +346 -0
- package/examples/yaml-workflows/05-order-processing.yaml +146 -0
- package/examples/yaml-workflows/06-activity-test.yaml +71 -0
- package/examples/yaml-workflows/07-activity-simple-test.yaml +43 -0
- package/examples/yaml-workflows/08-file-processing.yaml +141 -0
- package/examples/yaml-workflows/09-http-request.yaml +137 -0
- package/examples/yaml-workflows/README.md +211 -0
- package/package.json +38 -0
- package/src/actions/code-execution.schema.ts +27 -0
- package/src/actions/code-execution.ts +218 -0
- package/src/actions/generate-file.test.ts +516 -0
- package/src/actions/generate-file.ts +166 -0
- package/src/actions/http-request.test.ts +784 -0
- package/src/actions/http-request.ts +228 -0
- package/src/actions/index.ts +20 -0
- package/src/actions/parse-file.test.ts +448 -0
- package/src/actions/parse-file.ts +139 -0
- package/src/actions/python-script.test.ts +439 -0
- package/src/actions/python-script.ts +154 -0
- package/src/base-node.test.ts +511 -0
- package/src/base-node.ts +605 -0
- package/src/behavior-tree.test.ts +431 -0
- package/src/behavior-tree.ts +283 -0
- package/src/blackboard.test.ts +222 -0
- package/src/blackboard.ts +192 -0
- package/src/composites/conditional.schema.ts +19 -0
- package/src/composites/conditional.test.ts +309 -0
- package/src/composites/conditional.ts +129 -0
- package/src/composites/for-each.schema.ts +23 -0
- package/src/composites/for-each.test.ts +254 -0
- package/src/composites/for-each.ts +132 -0
- package/src/composites/index.ts +15 -0
- package/src/composites/memory-sequence.schema.ts +19 -0
- package/src/composites/memory-sequence.test.ts +223 -0
- package/src/composites/memory-sequence.ts +98 -0
- package/src/composites/parallel.schema.ts +28 -0
- package/src/composites/parallel.test.ts +502 -0
- package/src/composites/parallel.ts +157 -0
- package/src/composites/reactive-sequence.schema.ts +19 -0
- package/src/composites/reactive-sequence.test.ts +170 -0
- package/src/composites/reactive-sequence.ts +85 -0
- package/src/composites/recovery.schema.ts +19 -0
- package/src/composites/recovery.test.ts +366 -0
- package/src/composites/recovery.ts +90 -0
- package/src/composites/selector.schema.ts +19 -0
- package/src/composites/selector.test.ts +387 -0
- package/src/composites/selector.ts +85 -0
- package/src/composites/sequence.schema.ts +19 -0
- package/src/composites/sequence.test.ts +337 -0
- package/src/composites/sequence.ts +72 -0
- package/src/composites/sub-tree.schema.ts +21 -0
- package/src/composites/sub-tree.test.ts +893 -0
- package/src/composites/sub-tree.ts +177 -0
- package/src/composites/while.schema.ts +24 -0
- package/src/composites/while.test.ts +381 -0
- package/src/composites/while.ts +149 -0
- package/src/data-store/index.ts +10 -0
- package/src/data-store/memory-store.ts +161 -0
- package/src/data-store/types.ts +94 -0
- package/src/debug/breakpoint.test.ts +47 -0
- package/src/debug/breakpoint.ts +30 -0
- package/src/debug/index.ts +17 -0
- package/src/debug/resume-point.test.ts +49 -0
- package/src/debug/resume-point.ts +29 -0
- package/src/decorators/delay.schema.ts +21 -0
- package/src/decorators/delay.test.ts +261 -0
- package/src/decorators/delay.ts +140 -0
- package/src/decorators/force-result.schema.ts +32 -0
- package/src/decorators/force-result.test.ts +133 -0
- package/src/decorators/force-result.ts +63 -0
- package/src/decorators/index.ts +13 -0
- package/src/decorators/invert.schema.ts +19 -0
- package/src/decorators/invert.test.ts +135 -0
- package/src/decorators/invert.ts +42 -0
- package/src/decorators/keep-running.schema.ts +20 -0
- package/src/decorators/keep-running.test.ts +105 -0
- package/src/decorators/keep-running.ts +49 -0
- package/src/decorators/precondition.schema.ts +19 -0
- package/src/decorators/precondition.test.ts +351 -0
- package/src/decorators/precondition.ts +139 -0
- package/src/decorators/repeat.schema.ts +21 -0
- package/src/decorators/repeat.test.ts +187 -0
- package/src/decorators/repeat.ts +94 -0
- package/src/decorators/run-once.schema.ts +19 -0
- package/src/decorators/run-once.test.ts +140 -0
- package/src/decorators/run-once.ts +61 -0
- package/src/decorators/soft-assert.schema.ts +19 -0
- package/src/decorators/soft-assert.test.ts +107 -0
- package/src/decorators/soft-assert.ts +68 -0
- package/src/decorators/timeout.schema.ts +21 -0
- package/src/decorators/timeout.test.ts +274 -0
- package/src/decorators/timeout.ts +159 -0
- package/src/errors.test.ts +63 -0
- package/src/errors.ts +34 -0
- package/src/events.test.ts +347 -0
- package/src/events.ts +183 -0
- package/src/index.ts +80 -0
- package/src/integrations/index.ts +30 -0
- package/src/integrations/integration-action.test.ts +571 -0
- package/src/integrations/integration-action.ts +233 -0
- package/src/integrations/piece-executor.ts +320 -0
- package/src/observability/execution-tracker.ts +320 -0
- package/src/observability/index.ts +23 -0
- package/src/observability/sinks.ts +138 -0
- package/src/observability/types.ts +130 -0
- package/src/registry-utils.ts +147 -0
- package/src/registry.test.ts +466 -0
- package/src/registry.ts +334 -0
- package/src/schemas/base.schema.ts +104 -0
- package/src/schemas/index.ts +223 -0
- package/src/schemas/integration.test.ts +238 -0
- package/src/schemas/tree-definition.schema.ts +170 -0
- package/src/schemas/validation.test.ts +146 -0
- package/src/schemas/validation.ts +122 -0
- package/src/scripting/index.ts +22 -0
- package/src/templates/template-loader.test.ts +281 -0
- package/src/templates/template-loader.ts +152 -0
- package/src/temporal-integration.test.ts +213 -0
- package/src/test-nodes.ts +259 -0
- package/src/types.ts +503 -0
- package/src/utilities/index.ts +17 -0
- package/src/utilities/log-message.test.ts +275 -0
- package/src/utilities/log-message.ts +134 -0
- package/src/utilities/regex-extract.test.ts +138 -0
- package/src/utilities/regex-extract.ts +108 -0
- package/src/utilities/variable-resolver.test.ts +416 -0
- package/src/utilities/variable-resolver.ts +318 -0
- package/src/utils/error-handler.test.ts +117 -0
- package/src/utils/error-handler.ts +48 -0
- package/src/utils/signal-check.test.ts +234 -0
- package/src/utils/signal-check.ts +140 -0
- package/src/yaml/errors.ts +143 -0
- package/src/yaml/index.ts +30 -0
- package/src/yaml/loader.ts +39 -0
- package/src/yaml/parser.ts +286 -0
- package/src/yaml/validation/semantic-validator.ts +196 -0
- package/templates/google-sheets/insert-row.yaml +76 -0
- package/templates/notification-sender.yaml +33 -0
- package/templates/order-validation.yaml +44 -0
- package/tsconfig.json +24 -0
- package/vitest.config.ts +25 -0
- package/workflows/order-processor.yaml +59 -0
- 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
|
+
}
|