@nordsym/apiclaw 1.5.17 → 1.5.19

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 (228) hide show
  1. package/convex/http.js.map +1 -1
  2. package/convex/http.ts +516 -0
  3. package/dist/analytics.d.ts +0 -4
  4. package/dist/analytics.d.ts.map +1 -1
  5. package/dist/analytics.js +0 -1
  6. package/dist/analytics.js.map +1 -1
  7. package/dist/bin.js +1 -1
  8. package/dist/cli/commands/mcp-install.d.ts.map +1 -1
  9. package/dist/cli/commands/mcp-install.js +8 -87
  10. package/dist/cli/commands/mcp-install.js.map +1 -1
  11. package/dist/cli/index.js +0 -7
  12. package/dist/credentials.d.ts.map +1 -1
  13. package/dist/credentials.js +38 -43
  14. package/dist/credentials.js.map +1 -1
  15. package/dist/discovery.d.ts.map +1 -1
  16. package/dist/discovery.js +82 -191
  17. package/dist/discovery.js.map +1 -1
  18. package/dist/http-api.d.ts.map +1 -1
  19. package/dist/http-api.js +33 -17
  20. package/dist/http-api.js.map +1 -1
  21. package/dist/proxy.js +1 -1
  22. package/dist/proxy.js.map +1 -1
  23. package/landing/next-env.d.ts +0 -1
  24. package/landing/src/app/api/auth/magic-link/route.ts +1 -1
  25. package/landing/src/app/auth/verify/page.tsx +0 -6
  26. package/landing/src/app/dashboard/verify/page.tsx +0 -6
  27. package/landing/src/app/join/page.tsx +0 -6
  28. package/landing/src/app/layout.tsx +2 -2
  29. package/landing/src/app/login/page.tsx +1 -1
  30. package/landing/src/app/mou/[partnerId]/page.tsx +0 -6
  31. package/landing/src/app/page.tsx +18 -39
  32. package/landing/src/app/providers/dashboard/[apiId]/actions/[actionId]/edit/page.tsx +0 -6
  33. package/landing/src/app/providers/dashboard/[apiId]/actions/new/page.tsx +0 -5
  34. package/landing/src/app/providers/dashboard/[apiId]/actions/page.tsx +0 -5
  35. package/landing/src/app/providers/dashboard/[apiId]/direct-call/page.tsx +1 -6
  36. package/landing/src/app/providers/dashboard/[apiId]/page.tsx +0 -5
  37. package/landing/src/app/providers/dashboard/[apiId]/test/page.tsx +0 -5
  38. package/landing/src/app/providers/dashboard/layout.tsx +6 -6
  39. package/landing/src/app/providers/dashboard/login/page.tsx +1 -1
  40. package/landing/src/app/providers/dashboard/page.tsx +1 -1
  41. package/landing/src/app/providers/dashboard/verify/page.tsx +0 -6
  42. package/landing/src/app/providers/layout.tsx +1 -1
  43. package/landing/src/app/upgrade/page.tsx +0 -6
  44. package/landing/src/app/workspace/page.tsx +0 -6
  45. package/landing/src/components/HeroTabs.tsx +2 -2
  46. package/landing/src/components/{Workspace.tsx → ProviderDashboard.tsx} +2 -2
  47. package/landing/src/components/VideoDemo.tsx +10 -21
  48. package/landing/src/lib/mock-data.ts +1 -1
  49. package/landing/src/lib/stats.json +1 -1
  50. package/package.json +3 -8
  51. package/src/analytics.ts +0 -5
  52. package/src/bin.ts +1 -1
  53. package/src/cli/commands/mcp-install.ts +8 -90
  54. package/src/cli/index.ts +0 -8
  55. package/src/credentials.ts +39 -44
  56. package/src/discovery.ts +82 -191
  57. package/src/http-api.ts +34 -18
  58. package/src/proxy.ts +1 -1
  59. package/APILAYER_STATUS_2026-03-24.md +0 -38
  60. package/CHANGELOG-WHITELIST-V2.md +0 -269
  61. package/HIVR-WHITELIST-STATUS.md +0 -205
  62. package/HIVR-WHITELIST.md +0 -148
  63. package/TERMINOLOGY-AUDIT.md +0 -99
  64. package/TERMINOLOGY-FIXED.md +0 -74
  65. package/VIDEO-DEMO-GUIDE.md +0 -82
  66. package/WHITELIST-ARCHITECTURE.md +0 -379
  67. package/api/discover.ts +0 -71
  68. package/api/health.ts +0 -20
  69. package/convex/adminActivate.d.ts +0 -3
  70. package/convex/adminActivate.js +0 -47
  71. package/convex/adminStats.d.ts +0 -3
  72. package/convex/adminStats.js +0 -42
  73. package/convex/agents.d.ts +0 -54
  74. package/convex/agents.js +0 -499
  75. package/convex/analytics.d.ts +0 -5
  76. package/convex/analytics.js +0 -166
  77. package/convex/billing.d.ts +0 -88
  78. package/convex/billing.js +0 -655
  79. package/convex/capabilities.d.ts +0 -9
  80. package/convex/capabilities.js +0 -145
  81. package/convex/chains.d.ts +0 -67
  82. package/convex/chains.js +0 -1042
  83. package/convex/credits.d.ts +0 -25
  84. package/convex/credits.js +0 -186
  85. package/convex/crons.d.ts +0 -3
  86. package/convex/crons.js +0 -17
  87. package/convex/directCall.d.ts +0 -72
  88. package/convex/directCall.js +0 -627
  89. package/convex/earnProgress.d.ts +0 -58
  90. package/convex/earnProgress.js +0 -649
  91. package/convex/email.d.ts +0 -14
  92. package/convex/email.js +0 -300
  93. package/convex/feedback.d.ts +0 -7
  94. package/convex/feedback.js +0 -227
  95. package/convex/http.d.ts +0 -3
  96. package/convex/http.js +0 -910
  97. package/convex/logs.d.ts +0 -38
  98. package/convex/logs.js +0 -487
  99. package/convex/mou.d.ts +0 -6
  100. package/convex/mou.js +0 -82
  101. package/convex/providerKeys.d.ts +0 -31
  102. package/convex/providerKeys.js +0 -257
  103. package/convex/providers.d.ts +0 -29
  104. package/convex/providers.js +0 -756
  105. package/convex/purchases.d.ts +0 -7
  106. package/convex/purchases.js +0 -157
  107. package/convex/ratelimit.d.ts +0 -4
  108. package/convex/ratelimit.js +0 -91
  109. package/convex/searchLogs.d.ts +0 -4
  110. package/convex/searchLogs.js +0 -129
  111. package/convex/spendAlerts.d.ts +0 -36
  112. package/convex/spendAlerts.js +0 -380
  113. package/convex/stripeActions.d.ts +0 -19
  114. package/convex/stripeActions.js +0 -411
  115. package/convex/teams.d.ts +0 -21
  116. package/convex/teams.js +0 -215
  117. package/convex/telemetry.d.ts +0 -4
  118. package/convex/telemetry.js +0 -74
  119. package/convex/usage.d.ts +0 -27
  120. package/convex/usage.js +0 -229
  121. package/convex/waitlist.d.ts +0 -4
  122. package/convex/waitlist.js +0 -49
  123. package/convex/webhooks.d.ts +0 -12
  124. package/convex/webhooks.js +0 -410
  125. package/convex/workspaces.d.ts +0 -29
  126. package/convex/workspaces.js +0 -880
  127. package/direct-test.mjs +0 -51
  128. package/dist/access-control.d.ts +0 -45
  129. package/dist/access-control.d.ts.map +0 -1
  130. package/dist/access-control.js +0 -142
  131. package/dist/access-control.js.map +0 -1
  132. package/dist/chain-types.d.ts +0 -187
  133. package/dist/chain-types.d.ts.map +0 -1
  134. package/dist/chain-types.js +0 -33
  135. package/dist/chain-types.js.map +0 -1
  136. package/dist/convex/adminActivate.js +0 -46
  137. package/dist/convex/adminStats.js +0 -41
  138. package/dist/convex/agents.js +0 -498
  139. package/dist/convex/analytics.js +0 -165
  140. package/dist/convex/billing.js +0 -654
  141. package/dist/convex/capabilities.js +0 -144
  142. package/dist/convex/chains.js +0 -1041
  143. package/dist/convex/credits.js +0 -185
  144. package/dist/convex/crons.js +0 -16
  145. package/dist/convex/directCall.js +0 -626
  146. package/dist/convex/earnProgress.js +0 -648
  147. package/dist/convex/email.js +0 -299
  148. package/dist/convex/feedback.js +0 -226
  149. package/dist/convex/http.js +0 -909
  150. package/dist/convex/logs.js +0 -486
  151. package/dist/convex/mou.js +0 -81
  152. package/dist/convex/providerKeys.js +0 -256
  153. package/dist/convex/providers.js +0 -755
  154. package/dist/convex/purchases.js +0 -156
  155. package/dist/convex/ratelimit.js +0 -90
  156. package/dist/convex/schema.js +0 -709
  157. package/dist/convex/searchLogs.js +0 -128
  158. package/dist/convex/spendAlerts.js +0 -379
  159. package/dist/convex/stripeActions.js +0 -410
  160. package/dist/convex/teams.js +0 -214
  161. package/dist/convex/telemetry.js +0 -73
  162. package/dist/convex/usage.js +0 -228
  163. package/dist/convex/waitlist.js +0 -48
  164. package/dist/convex/webhooks.js +0 -409
  165. package/dist/convex/workspaces.js +0 -879
  166. package/dist/hivr-whitelist.d.ts +0 -18
  167. package/dist/hivr-whitelist.d.ts.map +0 -1
  168. package/dist/hivr-whitelist.js +0 -95
  169. package/dist/hivr-whitelist.js.map +0 -1
  170. package/dist/http-server-minimal.d.ts +0 -7
  171. package/dist/http-server-minimal.d.ts.map +0 -1
  172. package/dist/http-server-minimal.js +0 -126
  173. package/dist/http-server-minimal.js.map +0 -1
  174. package/dist/product-whitelist.d.ts +0 -37
  175. package/dist/product-whitelist.d.ts.map +0 -1
  176. package/dist/product-whitelist.js +0 -203
  177. package/dist/product-whitelist.js.map +0 -1
  178. package/dist/src/analytics.js +0 -129
  179. package/dist/src/bin.js +0 -17
  180. package/dist/src/capability-router.js +0 -240
  181. package/dist/src/chainExecutor.js +0 -451
  182. package/dist/src/chainResolver.js +0 -518
  183. package/dist/src/cli/commands/doctor.js +0 -324
  184. package/dist/src/cli/commands/mcp-install.js +0 -255
  185. package/dist/src/cli/commands/restore.js +0 -259
  186. package/dist/src/cli/commands/setup.js +0 -205
  187. package/dist/src/cli/commands/uninstall.js +0 -188
  188. package/dist/src/cli/index.js +0 -111
  189. package/dist/src/cli.js +0 -302
  190. package/dist/src/confirmation.js +0 -240
  191. package/dist/src/credentials.js +0 -357
  192. package/dist/src/credits.js +0 -260
  193. package/dist/src/crypto.js +0 -66
  194. package/dist/src/discovery.js +0 -504
  195. package/dist/src/enterprise/env.js +0 -123
  196. package/dist/src/enterprise/script-generator.js +0 -460
  197. package/dist/src/execute-dynamic.js +0 -473
  198. package/dist/src/execute.js +0 -1727
  199. package/dist/src/index.js +0 -2062
  200. package/dist/src/metered.js +0 -80
  201. package/dist/src/open-apis.js +0 -276
  202. package/dist/src/proxy.js +0 -28
  203. package/dist/src/session.js +0 -86
  204. package/dist/src/stripe.js +0 -407
  205. package/dist/src/telemetry.js +0 -49
  206. package/dist/src/types.js +0 -2
  207. package/dist/src/utils/backup.js +0 -181
  208. package/dist/src/utils/config.js +0 -220
  209. package/dist/src/utils/os.js +0 -105
  210. package/dist/src/utils/paths.js +0 -159
  211. package/landing/pages/api/discover.ts +0 -43
  212. package/landing/pages/api/health.ts +0 -20
  213. package/scripts/test-whitelist-v2.sh +0 -128
  214. package/src/access-control.ts +0 -174
  215. package/src/hivr-whitelist.ts +0 -110
  216. package/src/http-server-minimal.ts +0 -154
  217. package/src/product-whitelist.ts +0 -246
  218. package/test-actual-handlers.ts +0 -92
  219. package/test-apilayer-all-14.ts +0 -249
  220. package/test-apilayer-fixed.ts +0 -248
  221. package/test-direct-endpoints.ts +0 -174
  222. package/test-exact-endpoints.ts +0 -144
  223. package/test-final.ts +0 -83
  224. package/test-full-routing.ts +0 -100
  225. package/test-handlers-correct.ts +0 -217
  226. package/test-numverify-key.ts +0 -41
  227. package/test-via-handlers.ts +0 -92
  228. package/test-worldnews.mjs +0 -26
@@ -1,240 +0,0 @@
1
- /**
2
- * APIClaw Capability Router
3
- * Routes capability requests to the best available provider
4
- */
5
- import { executeAPICall } from './execute.js';
6
- import { logAPICall } from './analytics.js';
7
- // Convex HTTP API for capability queries
8
- const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || 'https://adventurous-avocet-799.convex.cloud';
9
- /**
10
- * Query Convex for providers that support a capability
11
- */
12
- async function getProvidersForCapability(capabilityId, region) {
13
- try {
14
- const res = await fetch(`${CONVEX_URL}/api/query`, {
15
- method: 'POST',
16
- headers: { 'Content-Type': 'application/json' },
17
- body: JSON.stringify({
18
- path: 'capabilities:getProviders',
19
- args: { capabilityId, region },
20
- }),
21
- });
22
- if (!res.ok)
23
- return [];
24
- const data = await res.json();
25
- if (Array.isArray(data))
26
- return data;
27
- return (data.value || []);
28
- }
29
- catch (e) {
30
- console.error('Failed to fetch capability providers:', e);
31
- return [];
32
- }
33
- }
34
- /**
35
- * Map capability params to provider-specific params
36
- */
37
- function mapParams(params, mapping) {
38
- const result = {};
39
- for (const [capParam, value] of Object.entries(params)) {
40
- const providerParam = mapping[capParam] || capParam;
41
- result[providerParam] = value;
42
- }
43
- return result;
44
- }
45
- /**
46
- * Log capability usage to Convex
47
- */
48
- async function logCapabilityUsage(params) {
49
- try {
50
- await fetch(`${CONVEX_URL}/api/mutation`, {
51
- method: 'POST',
52
- headers: { 'Content-Type': 'application/json' },
53
- body: JSON.stringify({
54
- path: 'capabilities:logUsage',
55
- args: params,
56
- }),
57
- });
58
- }
59
- catch (e) {
60
- console.error('Failed to log capability usage:', e);
61
- }
62
- }
63
- /**
64
- * Execute a capability request with automatic provider selection and fallback
65
- */
66
- export async function executeCapability(capabilityId, action, params, userId, preferences = {}) {
67
- const startTime = Date.now();
68
- const enableFallback = preferences.fallback !== false; // Default true
69
- // Get providers for this capability
70
- const providers = await getProvidersForCapability(capabilityId, preferences.region);
71
- if (providers.length === 0) {
72
- return {
73
- success: false,
74
- capability: capabilityId,
75
- action,
76
- fallbackAttempted: false,
77
- error: `No providers available for capability: ${capabilityId}`,
78
- };
79
- }
80
- // Filter by max price if specified
81
- let filteredProviders = providers;
82
- if (preferences.maxPrice !== undefined) {
83
- filteredProviders = providers.filter(p => p.pricePerUnit <= preferences.maxPrice);
84
- }
85
- // Prefer specific provider if requested
86
- if (preferences.preferredProvider) {
87
- const preferred = filteredProviders.find(p => p.providerId === preferences.preferredProvider);
88
- if (preferred) {
89
- filteredProviders = [preferred, ...filteredProviders.filter(p => p.providerId !== preferences.preferredProvider)];
90
- }
91
- }
92
- if (filteredProviders.length === 0) {
93
- return {
94
- success: false,
95
- capability: capabilityId,
96
- action,
97
- fallbackAttempted: false,
98
- error: 'No providers match your preferences (region/price)',
99
- };
100
- }
101
- // Try providers in order
102
- let fallbackAttempted = false;
103
- let lastError = '';
104
- for (let i = 0; i < filteredProviders.length; i++) {
105
- const provider = filteredProviders[i];
106
- const isFirstAttempt = i === 0;
107
- if (!isFirstAttempt) {
108
- fallbackAttempted = true;
109
- }
110
- try {
111
- // Map params to provider-specific format
112
- const mappedParams = mapParams(params, provider.paramMapping || {});
113
- // Execute via existing executeAPICall
114
- const result = await executeAPICall(provider.providerId, action, mappedParams, userId);
115
- const latencyMs = Date.now() - startTime;
116
- if (result.success) {
117
- // Log successful usage
118
- logCapabilityUsage({
119
- capabilityId,
120
- providerId: provider.providerId,
121
- userId,
122
- action,
123
- success: true,
124
- fallbackUsed: fallbackAttempted,
125
- fallbackReason: fallbackAttempted ? lastError : undefined,
126
- latencyMs,
127
- cost: provider.pricePerUnit,
128
- currency: provider.currency,
129
- });
130
- // Also log to file-based analytics
131
- logAPICall({
132
- timestamp: new Date().toISOString(),
133
- provider: provider.providerId,
134
- action,
135
- type: 'direct',
136
- userId,
137
- success: true,
138
- latencyMs,
139
- });
140
- return {
141
- success: true,
142
- capability: capabilityId,
143
- action,
144
- providerUsed: provider.providerId,
145
- fallbackAttempted,
146
- fallbackReason: fallbackAttempted ? lastError : undefined,
147
- data: result.data,
148
- cost: provider.pricePerUnit,
149
- currency: provider.currency,
150
- latencyMs,
151
- };
152
- }
153
- // Provider returned error, try next
154
- lastError = result.error || 'Unknown error';
155
- if (!enableFallback) {
156
- break;
157
- }
158
- }
159
- catch (e) {
160
- lastError = e.message || 'Provider execution failed';
161
- if (!enableFallback) {
162
- break;
163
- }
164
- }
165
- }
166
- // All providers failed
167
- const latencyMs = Date.now() - startTime;
168
- logCapabilityUsage({
169
- capabilityId,
170
- providerId: filteredProviders[0].providerId,
171
- userId,
172
- action,
173
- success: false,
174
- fallbackUsed: fallbackAttempted,
175
- fallbackReason: lastError,
176
- latencyMs,
177
- cost: 0,
178
- currency: 'SEK',
179
- });
180
- return {
181
- success: false,
182
- capability: capabilityId,
183
- action,
184
- fallbackAttempted,
185
- error: `All providers failed. Last error: ${lastError}`,
186
- latencyMs,
187
- };
188
- }
189
- /**
190
- * List available capabilities
191
- */
192
- export async function listCapabilities() {
193
- try {
194
- const res = await fetch(`${CONVEX_URL}/api/query`, {
195
- method: 'POST',
196
- headers: { 'Content-Type': 'application/json' },
197
- body: JSON.stringify({
198
- path: 'capabilities:list',
199
- args: {},
200
- }),
201
- });
202
- if (!res.ok)
203
- return [];
204
- const data = await res.json();
205
- const capabilities = Array.isArray(data) ? data : (data.value || []);
206
- return capabilities.map(c => ({
207
- id: c.id,
208
- name: c.name,
209
- category: c.category,
210
- }));
211
- }
212
- catch (e) {
213
- return [];
214
- }
215
- }
216
- /**
217
- * Check if a capability exists
218
- */
219
- export async function hasCapability(capabilityId) {
220
- try {
221
- const res = await fetch(`${CONVEX_URL}/api/query`, {
222
- method: 'POST',
223
- headers: { 'Content-Type': 'application/json' },
224
- body: JSON.stringify({
225
- path: 'capabilities:getById',
226
- args: { id: capabilityId },
227
- }),
228
- });
229
- if (!res.ok)
230
- return false;
231
- const data = await res.json();
232
- if (data && typeof data === 'object' && 'value' in data) {
233
- return !!data.value;
234
- }
235
- return !!data;
236
- }
237
- catch (e) {
238
- return false;
239
- }
240
- }
@@ -1,451 +0,0 @@
1
- /**
2
- * APIClaw Chain Executor
3
- *
4
- * Orchestrates multi-step API chains with:
5
- * - Sequential execution
6
- * - Parallel batches
7
- * - Conditional branching
8
- * - ForEach loops
9
- * - Error handling and retry
10
- * - Full execution trace
11
- */
12
- import { executeAPICall } from './execute.js';
13
- import { resolveReferences, validateReferences, evaluateCondition, } from './chainResolver.js';
14
- // ============================================================================
15
- // MAIN EXECUTOR
16
- // ============================================================================
17
- /**
18
- * Execute a chain of API calls
19
- */
20
- export async function executeChain(chain, credentials, inputs, options = {}) {
21
- const chainId = chain.id || generateChainId();
22
- const startedAt = new Date().toISOString();
23
- const startTime = Date.now();
24
- // Initialize context
25
- const context = {
26
- results: {},
27
- inputs: inputs || {},
28
- currentIndex: 0,
29
- startedAt,
30
- env: process.env,
31
- };
32
- // Initialize result
33
- const result = {
34
- success: false,
35
- chainId,
36
- startedAt,
37
- completedAt: '',
38
- totalLatencyMs: 0,
39
- totalCost: { cents: 0 },
40
- results: {},
41
- completedSteps: [],
42
- trace: [],
43
- canResume: false,
44
- };
45
- try {
46
- // Validate inputs
47
- if (chain.inputs) {
48
- validateInputs(inputs || {}, chain.inputs);
49
- }
50
- // Validate references before execution
51
- const validation = validateReferences(chain.steps);
52
- if (!validation.valid) {
53
- const errorMsg = validation.errors.map(e => `${e.stepId}: ${e.message}`).join('; ');
54
- throw new Error(`Reference validation failed: ${errorMsg}`);
55
- }
56
- // Log warnings
57
- if (options.verbose && validation.warnings.length > 0) {
58
- console.log('[Chain] Warnings:', validation.warnings);
59
- }
60
- // Check limits
61
- if (chain.limits?.maxSteps && chain.steps.length > chain.limits.maxSteps) {
62
- throw new Error(`Chain exceeds maxSteps limit (${chain.steps.length} > ${chain.limits.maxSteps})`);
63
- }
64
- // Execute steps
65
- let lastStepResult;
66
- for (let i = 0; i < chain.steps.length; i++) {
67
- const step = chain.steps[i];
68
- context.currentIndex = i;
69
- // Check timeout
70
- if (chain.timeout && Date.now() - startTime > chain.timeout) {
71
- throw new TimeoutError(`Chain exceeded timeout of ${chain.timeout}ms`);
72
- }
73
- // Execute based on step type
74
- if (isParallelStep(step)) {
75
- lastStepResult = await executeParallelStep(step, context, credentials, options, result);
76
- }
77
- else if (isConditionalStep(step)) {
78
- lastStepResult = await executeConditionalStep(step, context, credentials, options, result);
79
- }
80
- else if (isForEachStep(step)) {
81
- lastStepResult = await executeForEachStep(step, context, credentials, options, result, chain.limits);
82
- }
83
- else {
84
- lastStepResult = await executeSingleStep(step, context, credentials, options, result, chain.errorPolicy);
85
- }
86
- }
87
- // Success!
88
- result.success = true;
89
- result.finalResult = lastStepResult;
90
- result.results = { ...context.results };
91
- }
92
- catch (error) {
93
- // Handle failure
94
- result.success = false;
95
- result.error = {
96
- stepId: result.failedStep?.stepId || 'unknown',
97
- code: error.code || 'CHAIN_ERROR',
98
- message: error.message,
99
- retryAfter: error.retryAfter,
100
- };
101
- result.results = { ...context.results };
102
- // Handle rollback for transactional mode
103
- if (chain.errorPolicy?.mode === 'transactional' && chain.errorPolicy.rollback) {
104
- await executeRollback(chain.errorPolicy.rollback, context, credentials, options, result);
105
- }
106
- // Can resume if we have completed steps
107
- result.canResume = result.completedSteps.length > 0;
108
- if (result.canResume) {
109
- result.resumeToken = `${chainId}_step_${result.completedSteps.length}`;
110
- }
111
- }
112
- // Finalize
113
- result.completedAt = new Date().toISOString();
114
- result.totalLatencyMs = Date.now() - startTime;
115
- return result;
116
- }
117
- // ============================================================================
118
- // STEP TYPE GUARDS
119
- // ============================================================================
120
- function isParallelStep(step) {
121
- return 'parallel' in step;
122
- }
123
- function isConditionalStep(step) {
124
- return 'if' in step;
125
- }
126
- function isForEachStep(step) {
127
- return 'forEach' in step;
128
- }
129
- // ============================================================================
130
- // SINGLE STEP EXECUTION
131
- // ============================================================================
132
- async function executeSingleStep(step, context, credentials, options, result, errorPolicy) {
133
- const stepStart = Date.now();
134
- const stepTrace = {
135
- stepId: step.id,
136
- stepIndex: context.currentIndex,
137
- provider: step.provider,
138
- action: step.action,
139
- startedAt: new Date().toISOString(),
140
- completedAt: '',
141
- latencyMs: 0,
142
- success: false,
143
- input: {},
144
- };
145
- options.onStepStart?.(step.id, context.currentIndex);
146
- try {
147
- // Resolve references in params
148
- const resolvedParams = step.params ? resolveReferences(step.params, context) : {};
149
- stepTrace.input = resolvedParams;
150
- // Execute with retry if configured
151
- const maxRetries = step.onError?.retry?.attempts || 0;
152
- let lastError;
153
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
154
- try {
155
- // Get customer key for this provider
156
- const customerKey = credentials.customerKeys?.[step.provider];
157
- // Execute the API call
158
- const apiResult = await executeAPICall(step.provider, step.action, resolvedParams, credentials.userId, customerKey);
159
- if (!apiResult.success) {
160
- throw new StepError(apiResult.error || 'API call failed', apiResult.code || 'PROVIDER_ERROR');
161
- }
162
- // Success!
163
- stepTrace.success = true;
164
- stepTrace.output = apiResult.data;
165
- stepTrace.cost = apiResult.cost ? { cents: Math.round(apiResult.cost * 100) } : undefined;
166
- // Update context
167
- context.results[step.id] = apiResult.data;
168
- context.prevStepId = step.id;
169
- // Update result
170
- result.completedSteps.push(step.id);
171
- if (stepTrace.cost) {
172
- result.totalCost.cents += stepTrace.cost.cents;
173
- }
174
- return apiResult.data;
175
- }
176
- catch (error) {
177
- lastError = error;
178
- stepTrace.retries = attempt;
179
- if (attempt < maxRetries) {
180
- // Wait before retry
181
- const backoff = step.onError?.retry?.backoff?.[attempt] || 1000 * Math.pow(2, attempt);
182
- await sleep(backoff);
183
- }
184
- }
185
- }
186
- // All retries exhausted
187
- throw lastError;
188
- }
189
- catch (error) {
190
- stepTrace.success = false;
191
- stepTrace.error = error.message;
192
- stepTrace.errorCode = error.code;
193
- // Handle error based on policy
194
- const shouldAbort = step.onError?.abort !== false && errorPolicy?.mode !== 'best-effort';
195
- // Try fallback if configured
196
- if (step.onError?.fallback && !options.dryRun) {
197
- try {
198
- const fallbackResult = await executeSingleStep(step.onError.fallback, context, credentials, options, result, errorPolicy);
199
- // Use fallback result as this step's result
200
- context.results[step.id] = fallbackResult;
201
- context.prevStepId = step.id;
202
- stepTrace.success = true;
203
- stepTrace.output = { fallback: true, result: fallbackResult };
204
- result.completedSteps.push(step.id);
205
- return fallbackResult;
206
- }
207
- catch (fallbackError) {
208
- // Fallback also failed
209
- stepTrace.error = `Original: ${error.message}; Fallback: ${fallbackError.message}`;
210
- }
211
- }
212
- result.failedStep = stepTrace;
213
- if (shouldAbort) {
214
- throw error;
215
- }
216
- // Best-effort mode: continue despite failure
217
- context.results[step.id] = { error: error.message };
218
- context.prevStepId = step.id;
219
- return { error: error.message };
220
- }
221
- finally {
222
- stepTrace.completedAt = new Date().toISOString();
223
- stepTrace.latencyMs = Date.now() - stepStart;
224
- result.trace.push(stepTrace);
225
- options.onStepComplete?.(stepTrace);
226
- }
227
- }
228
- // ============================================================================
229
- // PARALLEL EXECUTION
230
- // ============================================================================
231
- async function executeParallelStep(step, context, credentials, options, result) {
232
- const parallelResults = await Promise.all(step.parallel.map(async (ps, idx) => {
233
- // Create a copy of context for each parallel step
234
- const parallelContext = {
235
- ...context,
236
- currentIndex: context.currentIndex,
237
- };
238
- try {
239
- return await executeSingleStep(ps, parallelContext, credentials, options, result);
240
- }
241
- catch (error) {
242
- // Return error as result for best-effort
243
- return { error: error.message };
244
- }
245
- }));
246
- // Store parallel results
247
- context.parallelResults = parallelResults;
248
- // Copy results from parallel steps to main context
249
- for (let i = 0; i < step.parallel.length; i++) {
250
- const ps = step.parallel[i];
251
- context.results[ps.id] = parallelResults[i];
252
- }
253
- // Last parallel step becomes prev
254
- context.prevStepId = step.parallel[step.parallel.length - 1].id;
255
- return parallelResults;
256
- }
257
- // ============================================================================
258
- // CONDITIONAL EXECUTION
259
- // ============================================================================
260
- async function executeConditionalStep(step, context, credentials, options, result) {
261
- // Evaluate condition
262
- const conditionResult = evaluateCondition(step.if, context);
263
- // Choose branch
264
- const branch = conditionResult ? step.then : step.else;
265
- if (!branch) {
266
- // No else branch and condition is false
267
- return null;
268
- }
269
- // Execute branch (can be single step or array)
270
- const steps = Array.isArray(branch) ? branch : [branch];
271
- let lastResult;
272
- for (const branchStep of steps) {
273
- lastResult = await executeSingleStep(branchStep, context, credentials, options, result);
274
- }
275
- return lastResult;
276
- }
277
- // ============================================================================
278
- // FOREACH EXECUTION
279
- // ============================================================================
280
- async function executeForEachStep(step, context, credentials, options, result, limits) {
281
- // Resolve the array to iterate over
282
- const arrayRef = step.forEach;
283
- let items;
284
- // Handle reference vs direct array
285
- if (arrayRef.startsWith('$')) {
286
- // It's a reference - resolve it
287
- const resolved = resolveReferences({ items: arrayRef }, context);
288
- items = resolved.items;
289
- }
290
- else {
291
- // Try to parse as JSON array
292
- try {
293
- items = JSON.parse(arrayRef);
294
- }
295
- catch {
296
- throw new Error(`forEach value must be a reference or JSON array: ${arrayRef}`);
297
- }
298
- }
299
- if (!Array.isArray(items)) {
300
- throw new Error(`forEach target must resolve to an array, got: ${typeof items}`);
301
- }
302
- // Check max iterations
303
- if (limits?.maxSteps && items.length > limits.maxSteps) {
304
- throw new Error(`forEach exceeds maxSteps limit (${items.length} iterations)`);
305
- }
306
- const forEachResults = [];
307
- for (let i = 0; i < items.length; i++) {
308
- // Set up forEach context
309
- context.forEachItem = items[i];
310
- context.forEachIndex = i;
311
- context.forEachResults = forEachResults;
312
- // Create step with dynamic ID
313
- const dynamicStep = {
314
- ...step.do,
315
- id: step.do.id.replace(/\$index/g, String(i)),
316
- };
317
- const itemResult = await executeSingleStep(dynamicStep, context, credentials, options, result);
318
- forEachResults.push(itemResult);
319
- }
320
- // Clean up forEach context
321
- delete context.forEachItem;
322
- delete context.forEachIndex;
323
- context.forEachResults = forEachResults;
324
- // Store aggregate results
325
- context.results['$forEach'] = { results: forEachResults };
326
- return forEachResults;
327
- }
328
- // ============================================================================
329
- // ROLLBACK EXECUTION
330
- // ============================================================================
331
- async function executeRollback(rollbackSteps, context, credentials, options, result) {
332
- for (const rb of rollbackSteps) {
333
- try {
334
- const shouldRollback = evaluateCondition(rb.if, context);
335
- if (shouldRollback) {
336
- await executeSingleStep(rb.do, context, credentials, { ...options, verbose: false }, result);
337
- }
338
- }
339
- catch (error) {
340
- // Log but don't fail on rollback errors
341
- console.error('[Chain] Rollback failed:', error);
342
- }
343
- }
344
- }
345
- // ============================================================================
346
- // INPUT VALIDATION
347
- // ============================================================================
348
- function validateInputs(inputs, definitions) {
349
- for (const [name, def] of Object.entries(definitions)) {
350
- const value = inputs[name];
351
- // Check required
352
- if (def.required && value === undefined) {
353
- if (def.default !== undefined) {
354
- inputs[name] = def.default;
355
- }
356
- else {
357
- throw new Error(`Missing required input: ${name}`);
358
- }
359
- }
360
- // Apply default
361
- if (value === undefined && def.default !== undefined) {
362
- inputs[name] = def.default;
363
- }
364
- // Type check (if value provided)
365
- if (inputs[name] !== undefined) {
366
- const actualType = Array.isArray(inputs[name]) ? 'array' : typeof inputs[name];
367
- if (actualType !== def.type) {
368
- throw new Error(`Input '${name}' must be ${def.type}, got ${actualType}`);
369
- }
370
- }
371
- }
372
- }
373
- // ============================================================================
374
- // UTILITIES
375
- // ============================================================================
376
- function generateChainId() {
377
- return `chain_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
378
- }
379
- function sleep(ms) {
380
- return new Promise(resolve => setTimeout(resolve, ms));
381
- }
382
- // ============================================================================
383
- // CUSTOM ERRORS
384
- // ============================================================================
385
- export class StepError extends Error {
386
- constructor(message, code, retryAfter) {
387
- super(message);
388
- this.name = 'StepError';
389
- this.code = code;
390
- this.retryAfter = retryAfter;
391
- }
392
- }
393
- export class TimeoutError extends Error {
394
- constructor(message) {
395
- super(message);
396
- this.code = 'TIMEOUT';
397
- this.name = 'TimeoutError';
398
- }
399
- }
400
- // ============================================================================
401
- // CHAIN STATUS & RESUME (Stubs for future implementation)
402
- // ============================================================================
403
- // In-memory store for running chains (would be Redis/DB in production)
404
- const runningChains = new Map();
405
- /**
406
- * Get status of a running or completed chain
407
- */
408
- export async function getChainStatus(chainId) {
409
- const entry = runningChains.get(chainId);
410
- if (!entry) {
411
- return { chainId, status: 'not_found' };
412
- }
413
- return {
414
- chainId,
415
- status: entry.status,
416
- result: entry.result,
417
- };
418
- }
419
- /**
420
- * Resume a failed chain from a resume token
421
- */
422
- export async function resumeChain(resumeToken, chain, credentials, inputs, overrides, options = {}) {
423
- // Parse resume token: chain_xyz_step_N
424
- const match = resumeToken.match(/^(.+)_step_(\d+)$/);
425
- if (!match) {
426
- throw new Error(`Invalid resume token: ${resumeToken}`);
427
- }
428
- const [, chainId, stepIndexStr] = match;
429
- const resumeFromIndex = parseInt(stepIndexStr, 10);
430
- // Create a modified chain starting from the resume point
431
- const resumedChain = {
432
- ...chain,
433
- id: chainId,
434
- steps: chain.steps.slice(resumeFromIndex),
435
- };
436
- // Apply any overrides to the resumed steps
437
- if (overrides) {
438
- for (const step of resumedChain.steps) {
439
- if ('id' in step && overrides[step.id]) {
440
- step.params = { ...step.params, ...overrides[step.id] };
441
- }
442
- }
443
- }
444
- // Execute the resumed chain
445
- // Note: In a real implementation, we'd restore the context from the previous run
446
- return executeChain(resumedChain, credentials, inputs, options);
447
- }
448
- // ============================================================================
449
- // RE-EXPORTS
450
- // ============================================================================
451
- export { resolveReferences, validateReferences, evaluateCondition, } from './chainResolver.js';