@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,1041 +0,0 @@
1
- import { v } from "convex/values";
2
- import { mutation, query, action, internalMutation, internalAction } from "./_generated/server";
3
- import { internal } from "./_generated/api";
4
- // ============================================
5
- // HELPER: Generate resume token
6
- // ============================================
7
- function generateResumeToken(chainId, stepIndex) {
8
- const random = Math.random().toString(36).substring(2, 8);
9
- return `chain_${chainId.slice(-8)}_step_${stepIndex}_${random}`;
10
- }
11
- // ============================================
12
- // DASHBOARD QUERIES (for workspace chains page)
13
- // ============================================
14
- /**
15
- * Get chain executions for a workspace (authenticated via session token)
16
- */
17
- export const getChainExecutions = query({
18
- args: {
19
- token: v.string(),
20
- limit: v.optional(v.number()),
21
- status: v.optional(v.string()),
22
- },
23
- handler: async (ctx, args) => {
24
- // Validate session
25
- const session = await ctx.db
26
- .query("agentSessions")
27
- .withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
28
- .first();
29
- if (!session) {
30
- return { error: "Invalid session" };
31
- }
32
- // Get chains for workspace
33
- const allChains = await ctx.db
34
- .query("chains")
35
- .withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
36
- .order("desc")
37
- .collect();
38
- // Filter by status if provided
39
- let filteredChains = allChains;
40
- if (args.status && args.status !== "all") {
41
- filteredChains = allChains.filter((c) => c.status === args.status);
42
- }
43
- // Apply limit
44
- const limit = args.limit || 50;
45
- const chains = filteredChains.slice(0, limit);
46
- // For each chain, get step count from chainExecutions
47
- const chainsWithStepCount = await Promise.all(chains.map(async (chain) => {
48
- const steps = await ctx.db
49
- .query("chainExecutions")
50
- .withIndex("by_chainId", (q) => q.eq("chainId", chain._id))
51
- .collect();
52
- return {
53
- _id: chain._id,
54
- status: chain.status,
55
- currentStep: chain.currentStep,
56
- stepsCount: steps.length || chain.steps?.length || 0,
57
- totalCostCents: chain.totalCostCents || 0,
58
- totalLatencyMs: chain.totalLatencyMs || 0,
59
- error: chain.error,
60
- canResume: chain.canResume,
61
- resumeToken: chain.resumeToken,
62
- createdAt: chain.createdAt,
63
- startedAt: chain.startedAt,
64
- completedAt: chain.completedAt,
65
- };
66
- }));
67
- return chainsWithStepCount;
68
- },
69
- });
70
- /**
71
- * Get full trace for a single chain (authenticated via session token)
72
- */
73
- export const getChainTraceAuth = query({
74
- args: {
75
- token: v.string(),
76
- chainId: v.id("chains"),
77
- },
78
- handler: async (ctx, args) => {
79
- // Validate session
80
- const session = await ctx.db
81
- .query("agentSessions")
82
- .withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
83
- .first();
84
- if (!session) {
85
- return { error: "Invalid session" };
86
- }
87
- // Get the chain
88
- const chain = await ctx.db.get(args.chainId);
89
- if (!chain || chain.workspaceId !== session.workspaceId) {
90
- return { error: "Chain not found" };
91
- }
92
- // Get all step executions
93
- const executions = await ctx.db
94
- .query("chainExecutions")
95
- .withIndex("by_chainId", (q) => q.eq("chainId", args.chainId))
96
- .collect();
97
- // Sort by stepIndex
98
- executions.sort((a, b) => a.stepIndex - b.stepIndex);
99
- // Calculate total tokens saved (estimate: ~400 tokens per step avoided)
100
- const completedSteps = executions.filter((e) => e.status === "completed");
101
- const tokensSaved = completedSteps.length > 1 ? (completedSteps.length - 1) * 400 : 0;
102
- return {
103
- chain: {
104
- _id: chain._id,
105
- status: chain.status,
106
- currentStep: chain.currentStep,
107
- steps: chain.steps,
108
- results: chain.results,
109
- error: chain.error,
110
- continueOnError: chain.continueOnError,
111
- timeout: chain.timeout,
112
- canResume: chain.canResume,
113
- resumeToken: chain.resumeToken,
114
- totalCostCents: chain.totalCostCents || 0,
115
- totalLatencyMs: chain.totalLatencyMs || 0,
116
- createdAt: chain.createdAt,
117
- startedAt: chain.startedAt,
118
- completedAt: chain.completedAt,
119
- },
120
- executions: executions.map((e) => ({
121
- _id: e._id,
122
- stepId: e.stepId,
123
- stepIndex: e.stepIndex,
124
- status: e.status,
125
- input: e.input,
126
- output: e.output,
127
- latencyMs: e.latencyMs,
128
- costCents: e.costCents,
129
- error: e.error,
130
- parallelGroup: e.parallelGroup,
131
- createdAt: e.createdAt,
132
- startedAt: e.startedAt,
133
- completedAt: e.completedAt,
134
- })),
135
- tokensSaved,
136
- };
137
- },
138
- });
139
- /**
140
- * Resume a failed/paused chain (authenticated via session token)
141
- */
142
- export const resumeChainAuth = mutation({
143
- args: {
144
- token: v.string(),
145
- chainId: v.id("chains"),
146
- overrides: v.optional(v.any()),
147
- },
148
- handler: async (ctx, args) => {
149
- // Validate session
150
- const session = await ctx.db
151
- .query("agentSessions")
152
- .withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
153
- .first();
154
- if (!session) {
155
- return { error: "Invalid session" };
156
- }
157
- // Get the chain
158
- const chain = await ctx.db.get(args.chainId);
159
- if (!chain || chain.workspaceId !== session.workspaceId) {
160
- return { error: "Chain not found" };
161
- }
162
- if (!chain.canResume) {
163
- return { error: "Chain cannot be resumed" };
164
- }
165
- // Update chain status to pending (orchestrator will pick it up)
166
- await ctx.db.patch(args.chainId, {
167
- status: "pending",
168
- error: undefined,
169
- });
170
- return { success: true, chainId: args.chainId };
171
- },
172
- });
173
- /**
174
- * Get chain statistics for workspace (authenticated via session token)
175
- */
176
- export const getChainStatsAuth = query({
177
- args: {
178
- token: v.string(),
179
- },
180
- handler: async (ctx, args) => {
181
- // Validate session
182
- const session = await ctx.db
183
- .query("agentSessions")
184
- .withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
185
- .first();
186
- if (!session) {
187
- return { error: "Invalid session" };
188
- }
189
- const chains = await ctx.db
190
- .query("chains")
191
- .withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
192
- .collect();
193
- const total = chains.length;
194
- const completed = chains.filter((c) => c.status === "completed").length;
195
- const failed = chains.filter((c) => c.status === "failed").length;
196
- const running = chains.filter((c) => c.status === "running").length;
197
- const paused = chains.filter((c) => c.status === "paused").length;
198
- const totalCostCents = chains.reduce((acc, c) => acc + (c.totalCostCents || 0), 0);
199
- const totalLatencyMs = chains.reduce((acc, c) => acc + (c.totalLatencyMs || 0), 0);
200
- // Count total steps across all chains
201
- const allExecutions = await Promise.all(chains.map((c) => ctx.db
202
- .query("chainExecutions")
203
- .withIndex("by_chainId", (q) => q.eq("chainId", c._id))
204
- .collect()));
205
- const totalSteps = allExecutions.flat().length;
206
- return {
207
- total,
208
- completed,
209
- failed,
210
- running,
211
- paused,
212
- totalCostCents,
213
- totalLatencyMs,
214
- totalSteps,
215
- successRate: total > 0 ? Math.round((completed / total) * 100) : 0,
216
- };
217
- },
218
- });
219
- // ============================================
220
- // MUTATIONS
221
- // ============================================
222
- /**
223
- * Create a new chain execution record (internal)
224
- */
225
- export const createChainInternal = internalMutation({
226
- args: {
227
- workspaceId: v.id("workspaces"),
228
- steps: v.array(v.any()),
229
- continueOnError: v.optional(v.boolean()),
230
- timeout: v.optional(v.number()),
231
- },
232
- handler: async (ctx, args) => {
233
- const now = Date.now();
234
- // Validate steps have required fields
235
- for (let i = 0; i < args.steps.length; i++) {
236
- const step = args.steps[i];
237
- if (step.parallel) {
238
- for (const pStep of step.parallel) {
239
- if (!pStep.id || !pStep.provider || !pStep.action) {
240
- throw new Error(`Parallel step at index ${i} missing required fields (id, provider, action)`);
241
- }
242
- }
243
- }
244
- else if (!step.id || !step.provider || !step.action) {
245
- throw new Error(`Step at index ${i} missing required fields (id, provider, action)`);
246
- }
247
- }
248
- // Create chain record
249
- const chainId = await ctx.db.insert("chains", {
250
- workspaceId: args.workspaceId,
251
- steps: args.steps,
252
- status: "pending",
253
- currentStep: 0,
254
- results: {},
255
- continueOnError: args.continueOnError ?? false,
256
- timeout: args.timeout,
257
- totalCostCents: 0,
258
- totalLatencyMs: 0,
259
- createdAt: now,
260
- });
261
- // Create execution records for each step
262
- for (let i = 0; i < args.steps.length; i++) {
263
- const step = args.steps[i];
264
- if (step.parallel) {
265
- const parallelGroup = `parallel_${i}_${Date.now()}`;
266
- for (const pStep of step.parallel) {
267
- await ctx.db.insert("chainExecutions", {
268
- chainId,
269
- stepId: pStep.id,
270
- stepIndex: i,
271
- status: "pending",
272
- parallelGroup,
273
- createdAt: now,
274
- });
275
- }
276
- }
277
- else {
278
- await ctx.db.insert("chainExecutions", {
279
- chainId,
280
- stepId: step.id,
281
- stepIndex: i,
282
- status: "pending",
283
- createdAt: now,
284
- });
285
- }
286
- }
287
- return chainId;
288
- },
289
- });
290
- /**
291
- * Create a new chain execution record (public API)
292
- */
293
- export const createChain = mutation({
294
- args: {
295
- workspaceId: v.id("workspaces"),
296
- steps: v.array(v.any()),
297
- continueOnError: v.optional(v.boolean()),
298
- timeout: v.optional(v.number()),
299
- },
300
- handler: async (ctx, args) => {
301
- const now = Date.now();
302
- // Validate steps
303
- for (let i = 0; i < args.steps.length; i++) {
304
- const step = args.steps[i];
305
- if (step.parallel) {
306
- for (const pStep of step.parallel) {
307
- if (!pStep.id || !pStep.provider || !pStep.action) {
308
- throw new Error(`Parallel step at index ${i} missing required fields (id, provider, action)`);
309
- }
310
- }
311
- }
312
- else if (!step.id || !step.provider || !step.action) {
313
- throw new Error(`Step at index ${i} missing required fields (id, provider, action)`);
314
- }
315
- }
316
- const chainId = await ctx.db.insert("chains", {
317
- workspaceId: args.workspaceId,
318
- steps: args.steps,
319
- status: "pending",
320
- currentStep: 0,
321
- results: {},
322
- continueOnError: args.continueOnError ?? false,
323
- timeout: args.timeout,
324
- totalCostCents: 0,
325
- totalLatencyMs: 0,
326
- createdAt: now,
327
- });
328
- // Create execution records
329
- for (let i = 0; i < args.steps.length; i++) {
330
- const step = args.steps[i];
331
- if (step.parallel) {
332
- const parallelGroup = `parallel_${i}_${Date.now()}`;
333
- for (const pStep of step.parallel) {
334
- await ctx.db.insert("chainExecutions", {
335
- chainId,
336
- stepId: pStep.id,
337
- stepIndex: i,
338
- status: "pending",
339
- parallelGroup,
340
- createdAt: now,
341
- });
342
- }
343
- }
344
- else {
345
- await ctx.db.insert("chainExecutions", {
346
- chainId,
347
- stepId: step.id,
348
- stepIndex: i,
349
- status: "pending",
350
- createdAt: now,
351
- });
352
- }
353
- }
354
- return chainId;
355
- },
356
- });
357
- /**
358
- * Create chain from template
359
- */
360
- export const createChainFromTemplate = mutation({
361
- args: {
362
- workspaceId: v.id("workspaces"),
363
- templateName: v.string(),
364
- inputs: v.optional(v.any()),
365
- },
366
- handler: async (ctx, args) => {
367
- const template = await ctx.db
368
- .query("chainTemplates")
369
- .withIndex("by_name", (q) => q.eq("workspaceId", args.workspaceId).eq("name", args.templateName))
370
- .first();
371
- if (!template) {
372
- throw new Error(`Template '${args.templateName}' not found`);
373
- }
374
- const now = Date.now();
375
- const chainId = await ctx.db.insert("chains", {
376
- workspaceId: args.workspaceId,
377
- steps: template.chain,
378
- status: "pending",
379
- currentStep: 0,
380
- results: {},
381
- totalCostCents: 0,
382
- totalLatencyMs: 0,
383
- createdAt: now,
384
- });
385
- for (let i = 0; i < template.chain.length; i++) {
386
- const step = template.chain[i];
387
- if (step.parallel) {
388
- const parallelGroup = `parallel_${i}_${Date.now()}`;
389
- for (const pStep of step.parallel) {
390
- await ctx.db.insert("chainExecutions", {
391
- chainId,
392
- stepId: pStep.id,
393
- stepIndex: i,
394
- status: "pending",
395
- parallelGroup,
396
- createdAt: now,
397
- });
398
- }
399
- }
400
- else {
401
- await ctx.db.insert("chainExecutions", {
402
- chainId,
403
- stepId: step.id,
404
- stepIndex: i,
405
- status: "pending",
406
- createdAt: now,
407
- });
408
- }
409
- }
410
- await ctx.db.patch(template._id, {
411
- useCount: (template.useCount || 0) + 1,
412
- lastUsedAt: now,
413
- });
414
- return chainId;
415
- },
416
- });
417
- /**
418
- * Execute a single step and store the result
419
- */
420
- export const executeStep = internalMutation({
421
- args: {
422
- chainId: v.id("chains"),
423
- stepId: v.string(),
424
- stepIndex: v.number(),
425
- input: v.any(),
426
- },
427
- handler: async (ctx, args) => {
428
- const now = Date.now();
429
- const execution = await ctx.db
430
- .query("chainExecutions")
431
- .withIndex("by_chainId_stepId", (q) => q.eq("chainId", args.chainId).eq("stepId", args.stepId))
432
- .first();
433
- if (!execution) {
434
- throw new Error(`No execution record found for step ${args.stepId}`);
435
- }
436
- await ctx.db.patch(execution._id, {
437
- status: "running",
438
- input: args.input,
439
- startedAt: now,
440
- });
441
- const chain = await ctx.db.get(args.chainId);
442
- if (chain && chain.status === "pending") {
443
- await ctx.db.patch(args.chainId, {
444
- status: "running",
445
- startedAt: now,
446
- });
447
- }
448
- return execution._id;
449
- },
450
- });
451
- /**
452
- * Record step completion with result
453
- */
454
- export const completeStep = internalMutation({
455
- args: {
456
- chainId: v.id("chains"),
457
- stepId: v.string(),
458
- output: v.any(),
459
- latencyMs: v.number(),
460
- costCents: v.number(),
461
- },
462
- handler: async (ctx, args) => {
463
- const now = Date.now();
464
- const execution = await ctx.db
465
- .query("chainExecutions")
466
- .withIndex("by_chainId_stepId", (q) => q.eq("chainId", args.chainId).eq("stepId", args.stepId))
467
- .first();
468
- if (!execution) {
469
- throw new Error(`No execution record found for step ${args.stepId}`);
470
- }
471
- await ctx.db.patch(execution._id, {
472
- status: "completed",
473
- output: args.output,
474
- latencyMs: args.latencyMs,
475
- costCents: args.costCents,
476
- completedAt: now,
477
- });
478
- const chain = await ctx.db.get(args.chainId);
479
- if (chain) {
480
- const results = { ...(chain.results || {}), [args.stepId]: args.output };
481
- const totalCost = (chain.totalCostCents || 0) + args.costCents;
482
- const totalLatency = (chain.totalLatencyMs || 0) + args.latencyMs;
483
- await ctx.db.patch(args.chainId, {
484
- results,
485
- totalCostCents: totalCost,
486
- totalLatencyMs: totalLatency,
487
- });
488
- }
489
- return { success: true };
490
- },
491
- });
492
- /**
493
- * Advance chain to next step
494
- */
495
- export const advanceChain = internalMutation({
496
- args: {
497
- chainId: v.id("chains"),
498
- },
499
- handler: async (ctx, args) => {
500
- const chain = await ctx.db.get(args.chainId);
501
- if (!chain) {
502
- throw new Error("Chain not found");
503
- }
504
- const nextStep = chain.currentStep + 1;
505
- if (nextStep >= chain.steps.length) {
506
- return { complete: true, nextStep: null };
507
- }
508
- await ctx.db.patch(args.chainId, {
509
- currentStep: nextStep,
510
- });
511
- const resumeToken = generateResumeToken(args.chainId, nextStep);
512
- await ctx.db.patch(args.chainId, {
513
- resumeToken,
514
- canResume: true,
515
- });
516
- return {
517
- complete: false,
518
- nextStep,
519
- nextStepDef: chain.steps[nextStep],
520
- };
521
- },
522
- });
523
- /**
524
- * Handle chain failure
525
- */
526
- export const failChain = internalMutation({
527
- args: {
528
- chainId: v.id("chains"),
529
- stepId: v.string(),
530
- error: v.object({
531
- code: v.string(),
532
- message: v.string(),
533
- retryAfter: v.optional(v.number()),
534
- }),
535
- },
536
- handler: async (ctx, args) => {
537
- const now = Date.now();
538
- const execution = await ctx.db
539
- .query("chainExecutions")
540
- .withIndex("by_chainId_stepId", (q) => q.eq("chainId", args.chainId).eq("stepId", args.stepId))
541
- .first();
542
- if (execution) {
543
- await ctx.db.patch(execution._id, {
544
- status: "failed",
545
- error: {
546
- code: args.error.code,
547
- message: args.error.message,
548
- },
549
- completedAt: now,
550
- });
551
- }
552
- const chain = await ctx.db.get(args.chainId);
553
- if (!chain) {
554
- throw new Error("Chain not found");
555
- }
556
- const resumeToken = generateResumeToken(args.chainId, chain.currentStep);
557
- await ctx.db.patch(args.chainId, {
558
- status: "failed",
559
- error: {
560
- stepId: args.stepId,
561
- ...args.error,
562
- },
563
- resumeToken,
564
- canResume: true,
565
- completedAt: now,
566
- });
567
- return {
568
- resumeToken,
569
- partialResults: chain.results,
570
- };
571
- },
572
- });
573
- /**
574
- * Resume chain from failed step (public mutation)
575
- */
576
- export const resumeChain = mutation({
577
- args: {
578
- resumeToken: v.string(),
579
- overrides: v.optional(v.any()),
580
- },
581
- handler: async (ctx, args) => {
582
- const chain = await ctx.db
583
- .query("chains")
584
- .withIndex("by_resumeToken", (q) => q.eq("resumeToken", args.resumeToken))
585
- .first();
586
- if (!chain) {
587
- throw new Error("Invalid or expired resume token");
588
- }
589
- if (!chain.canResume) {
590
- throw new Error("Chain cannot be resumed");
591
- }
592
- const executions = await ctx.db
593
- .query("chainExecutions")
594
- .withIndex("by_chainId_stepIndex", (q) => q.eq("chainId", chain._id).eq("stepIndex", chain.currentStep))
595
- .collect();
596
- for (const exec of executions) {
597
- if (exec.status === "failed") {
598
- await ctx.db.patch(exec._id, {
599
- status: "pending",
600
- error: undefined,
601
- startedAt: undefined,
602
- completedAt: undefined,
603
- });
604
- }
605
- }
606
- await ctx.db.patch(chain._id, {
607
- status: "pending",
608
- error: undefined,
609
- resumeToken: undefined,
610
- canResume: false,
611
- });
612
- return {
613
- chainId: chain._id,
614
- resumeFromStep: chain.currentStep,
615
- steps: chain.steps,
616
- results: chain.results,
617
- overrides: args.overrides,
618
- };
619
- },
620
- });
621
- /**
622
- * Mark chain as completed
623
- */
624
- export const completeChain = internalMutation({
625
- args: {
626
- chainId: v.id("chains"),
627
- },
628
- handler: async (ctx, args) => {
629
- const now = Date.now();
630
- const chain = await ctx.db.get(args.chainId);
631
- if (!chain) {
632
- throw new Error("Chain not found");
633
- }
634
- await ctx.db.patch(args.chainId, {
635
- status: "completed",
636
- canResume: false,
637
- resumeToken: undefined,
638
- completedAt: now,
639
- });
640
- return {
641
- success: true,
642
- results: chain.results,
643
- totalCostCents: chain.totalCostCents,
644
- totalLatencyMs: chain.totalLatencyMs,
645
- };
646
- },
647
- });
648
- /**
649
- * Pause chain execution
650
- */
651
- export const pauseChain = mutation({
652
- args: {
653
- chainId: v.id("chains"),
654
- },
655
- handler: async (ctx, args) => {
656
- const chain = await ctx.db.get(args.chainId);
657
- if (!chain) {
658
- throw new Error("Chain not found");
659
- }
660
- if (chain.status !== "running") {
661
- throw new Error("Can only pause running chains");
662
- }
663
- const resumeToken = generateResumeToken(args.chainId, chain.currentStep);
664
- await ctx.db.patch(args.chainId, {
665
- status: "paused",
666
- resumeToken,
667
- canResume: true,
668
- });
669
- return { resumeToken };
670
- },
671
- });
672
- // ============================================
673
- // CHAIN TEMPLATE MUTATIONS
674
- // ============================================
675
- export const saveChainTemplate = mutation({
676
- args: {
677
- id: v.optional(v.id("chainTemplates")),
678
- workspaceId: v.id("workspaces"),
679
- name: v.string(),
680
- description: v.optional(v.string()),
681
- inputs: v.optional(v.any()),
682
- chain: v.array(v.any()),
683
- },
684
- handler: async (ctx, args) => {
685
- const now = Date.now();
686
- if (args.id) {
687
- await ctx.db.patch(args.id, {
688
- name: args.name,
689
- description: args.description,
690
- inputs: args.inputs,
691
- chain: args.chain,
692
- updatedAt: now,
693
- });
694
- return args.id;
695
- }
696
- return await ctx.db.insert("chainTemplates", {
697
- workspaceId: args.workspaceId,
698
- name: args.name,
699
- description: args.description,
700
- inputs: args.inputs,
701
- chain: args.chain,
702
- useCount: 0,
703
- createdAt: now,
704
- updatedAt: now,
705
- });
706
- },
707
- });
708
- export const deleteChainTemplate = mutation({
709
- args: {
710
- id: v.id("chainTemplates"),
711
- },
712
- handler: async (ctx, args) => {
713
- await ctx.db.delete(args.id);
714
- return { success: true };
715
- },
716
- });
717
- // ============================================
718
- // QUERIES
719
- // ============================================
720
- export const getChain = query({
721
- args: {
722
- chainId: v.id("chains"),
723
- },
724
- handler: async (ctx, args) => {
725
- const chain = await ctx.db.get(args.chainId);
726
- if (!chain) {
727
- return null;
728
- }
729
- return {
730
- id: chain._id,
731
- status: chain.status,
732
- currentStep: chain.currentStep,
733
- totalSteps: chain.steps.length,
734
- results: chain.results,
735
- error: chain.error,
736
- canResume: chain.canResume,
737
- resumeToken: chain.resumeToken,
738
- totalCostCents: chain.totalCostCents,
739
- totalLatencyMs: chain.totalLatencyMs,
740
- createdAt: chain.createdAt,
741
- startedAt: chain.startedAt,
742
- completedAt: chain.completedAt,
743
- };
744
- },
745
- });
746
- export const getChainTrace = query({
747
- args: {
748
- chainId: v.id("chains"),
749
- },
750
- handler: async (ctx, args) => {
751
- const chain = await ctx.db.get(args.chainId);
752
- if (!chain) {
753
- return null;
754
- }
755
- const executions = await ctx.db
756
- .query("chainExecutions")
757
- .withIndex("by_chainId", (q) => q.eq("chainId", args.chainId))
758
- .collect();
759
- executions.sort((a, b) => a.stepIndex - b.stepIndex);
760
- const trace = executions.map((exec) => ({
761
- stepId: exec.stepId,
762
- stepIndex: exec.stepIndex,
763
- status: exec.status,
764
- parallelGroup: exec.parallelGroup,
765
- input: exec.input,
766
- output: exec.output,
767
- latencyMs: exec.latencyMs,
768
- costCents: exec.costCents,
769
- error: exec.error,
770
- startedAt: exec.startedAt,
771
- completedAt: exec.completedAt,
772
- }));
773
- const completedSteps = executions.filter((e) => e.status === "completed");
774
- const tokensSaved = completedSteps.length > 1 ? (completedSteps.length - 1) * 400 : 0;
775
- return {
776
- chainId: chain._id,
777
- workspaceId: chain.workspaceId,
778
- status: chain.status,
779
- steps: chain.steps,
780
- currentStep: chain.currentStep,
781
- results: chain.results,
782
- error: chain.error,
783
- trace,
784
- totalCostCents: chain.totalCostCents,
785
- totalLatencyMs: chain.totalLatencyMs,
786
- tokensSaved,
787
- canResume: chain.canResume,
788
- resumeToken: chain.resumeToken,
789
- createdAt: chain.createdAt,
790
- startedAt: chain.startedAt,
791
- completedAt: chain.completedAt,
792
- };
793
- },
794
- });
795
- export const listChains = query({
796
- args: {
797
- workspaceId: v.id("workspaces"),
798
- status: v.optional(v.string()),
799
- limit: v.optional(v.number()),
800
- },
801
- handler: async (ctx, args) => {
802
- const limit = args.limit ?? 50;
803
- const chains = await ctx.db
804
- .query("chains")
805
- .withIndex("by_workspaceId", (q) => q.eq("workspaceId", args.workspaceId))
806
- .order("desc")
807
- .take(limit);
808
- let filtered = chains;
809
- if (args.status && args.status !== "all") {
810
- filtered = chains.filter((c) => c.status === args.status);
811
- }
812
- return filtered.map((chain) => ({
813
- id: chain._id,
814
- status: chain.status,
815
- stepsCount: chain.steps.length,
816
- currentStep: chain.currentStep,
817
- totalCostCents: chain.totalCostCents,
818
- totalLatencyMs: chain.totalLatencyMs,
819
- error: chain.error,
820
- canResume: chain.canResume,
821
- createdAt: chain.createdAt,
822
- completedAt: chain.completedAt,
823
- }));
824
- },
825
- });
826
- export const listChainTemplates = query({
827
- args: {
828
- workspaceId: v.id("workspaces"),
829
- },
830
- handler: async (ctx, args) => {
831
- return await ctx.db
832
- .query("chainTemplates")
833
- .withIndex("by_workspaceId", (q) => q.eq("workspaceId", args.workspaceId))
834
- .collect();
835
- },
836
- });
837
- export const getChainTemplate = query({
838
- args: {
839
- workspaceId: v.id("workspaces"),
840
- name: v.string(),
841
- },
842
- handler: async (ctx, args) => {
843
- return await ctx.db
844
- .query("chainTemplates")
845
- .withIndex("by_name", (q) => q.eq("workspaceId", args.workspaceId).eq("name", args.name))
846
- .first();
847
- },
848
- });
849
- export const getChainStats = query({
850
- args: {
851
- workspaceId: v.id("workspaces"),
852
- },
853
- handler: async (ctx, args) => {
854
- const chains = await ctx.db
855
- .query("chains")
856
- .withIndex("by_workspaceId", (q) => q.eq("workspaceId", args.workspaceId))
857
- .collect();
858
- const total = chains.length;
859
- const completed = chains.filter((c) => c.status === "completed").length;
860
- const failed = chains.filter((c) => c.status === "failed").length;
861
- const running = chains.filter((c) => c.status === "running").length;
862
- const paused = chains.filter((c) => c.status === "paused").length;
863
- const totalCostCents = chains.reduce((acc, c) => acc + (c.totalCostCents || 0), 0);
864
- const totalLatencyMs = chains.reduce((acc, c) => acc + (c.totalLatencyMs || 0), 0);
865
- const allExecutions = await Promise.all(chains.map((c) => ctx.db
866
- .query("chainExecutions")
867
- .withIndex("by_chainId", (q) => q.eq("chainId", c._id))
868
- .collect()));
869
- const totalSteps = allExecutions.flat().length;
870
- return {
871
- total,
872
- completed,
873
- failed,
874
- running,
875
- paused,
876
- totalCostCents,
877
- totalLatencyMs,
878
- totalSteps,
879
- successRate: total > 0 ? Math.round((completed / total) * 100) : 0,
880
- };
881
- },
882
- });
883
- // ============================================
884
- // ACTIONS (Orchestration Logic)
885
- // ============================================
886
- export const runChain = action({
887
- args: {
888
- workspaceId: v.id("workspaces"),
889
- steps: v.array(v.any()),
890
- continueOnError: v.optional(v.boolean()),
891
- timeout: v.optional(v.number()),
892
- },
893
- handler: async (ctx, args) => {
894
- const startTime = Date.now();
895
- const timeout = args.timeout || 30000;
896
- const chainId = await ctx.runMutation(internal.chains.createChainInternal, {
897
- workspaceId: args.workspaceId,
898
- steps: args.steps,
899
- continueOnError: args.continueOnError,
900
- timeout: args.timeout,
901
- });
902
- const completedSteps = [];
903
- let currentResults = {};
904
- try {
905
- for (let i = 0; i < args.steps.length; i++) {
906
- if (Date.now() - startTime > timeout) {
907
- throw new Error("TIMEOUT: Chain execution exceeded timeout");
908
- }
909
- const step = args.steps[i];
910
- if (step.parallel && step.parallel.length > 0) {
911
- const parallelResults = await ctx.runAction(internal.chains.runParallelSteps, {
912
- chainId,
913
- steps: step.parallel,
914
- stepIndex: i,
915
- });
916
- for (const [stepId, result] of Object.entries(parallelResults)) {
917
- currentResults[stepId] = result;
918
- completedSteps.push(stepId);
919
- }
920
- }
921
- else {
922
- await ctx.runMutation(internal.chains.executeStep, {
923
- chainId,
924
- stepId: step.id,
925
- stepIndex: i,
926
- input: step.params,
927
- });
928
- const stepStartTime = Date.now();
929
- const result = await executeProviderCall(ctx, step);
930
- const latencyMs = Date.now() - stepStartTime;
931
- await ctx.runMutation(internal.chains.completeStep, {
932
- chainId,
933
- stepId: step.id,
934
- output: result,
935
- latencyMs,
936
- costCents: result.costCents || 0,
937
- });
938
- currentResults[step.id] = result;
939
- completedSteps.push(step.id);
940
- }
941
- await ctx.runMutation(internal.chains.advanceChain, { chainId });
942
- }
943
- const finalResult = await ctx.runMutation(internal.chains.completeChain, { chainId });
944
- return {
945
- success: true,
946
- chainId,
947
- results: currentResults,
948
- completedSteps,
949
- totalCostCents: finalResult.totalCostCents,
950
- totalLatencyMs: finalResult.totalLatencyMs,
951
- };
952
- }
953
- catch (error) {
954
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
955
- const errorCode = errorMessage.startsWith("TIMEOUT") ? "TIMEOUT" : "EXECUTION_ERROR";
956
- const currentStep = args.steps[completedSteps.length];
957
- const failedStepId = currentStep?.id || "unknown";
958
- const failureResult = await ctx.runMutation(internal.chains.failChain, {
959
- chainId,
960
- stepId: failedStepId,
961
- error: {
962
- code: errorCode,
963
- message: errorMessage,
964
- },
965
- });
966
- return {
967
- success: false,
968
- chainId,
969
- completedSteps,
970
- failedStep: {
971
- id: failedStepId,
972
- error: errorMessage,
973
- code: errorCode,
974
- },
975
- partialResults: failureResult.partialResults,
976
- canResume: true,
977
- resumeToken: failureResult.resumeToken,
978
- };
979
- }
980
- },
981
- });
982
- export const runParallelSteps = internalAction({
983
- args: {
984
- chainId: v.id("chains"),
985
- steps: v.array(v.any()),
986
- stepIndex: v.number(),
987
- },
988
- handler: async (ctx, args) => {
989
- const results = {};
990
- for (const step of args.steps) {
991
- await ctx.runMutation(internal.chains.executeStep, {
992
- chainId: args.chainId,
993
- stepId: step.id,
994
- stepIndex: args.stepIndex,
995
- input: step.params,
996
- });
997
- }
998
- const promises = args.steps.map(async (step) => {
999
- const startTime = Date.now();
1000
- try {
1001
- const result = await executeProviderCall(ctx, step);
1002
- const latencyMs = Date.now() - startTime;
1003
- await ctx.runMutation(internal.chains.completeStep, {
1004
- chainId: args.chainId,
1005
- stepId: step.id,
1006
- output: result,
1007
- latencyMs,
1008
- costCents: result.costCents || 0,
1009
- });
1010
- return { stepId: step.id, result };
1011
- }
1012
- catch (error) {
1013
- throw new Error(`Step ${step.id} failed: ${error instanceof Error ? error.message : "Unknown"}`);
1014
- }
1015
- });
1016
- const settledResults = await Promise.all(promises);
1017
- for (const { stepId, result } of settledResults) {
1018
- results[stepId] = result;
1019
- }
1020
- return results;
1021
- },
1022
- });
1023
- // ============================================
1024
- // HELPER: Execute provider call (placeholder)
1025
- // ============================================
1026
- async function executeProviderCall(ctx, step) {
1027
- // Simulate latency
1028
- await new Promise((resolve) => setTimeout(resolve, 50 + Math.random() * 100));
1029
- return {
1030
- success: true,
1031
- data: {
1032
- stepId: step.id,
1033
- provider: step.provider,
1034
- action: step.action,
1035
- params: step.params,
1036
- mockResult: `Executed ${step.action} on ${step.provider}`,
1037
- timestamp: Date.now(),
1038
- },
1039
- costCents: Math.ceil(Math.random() * 5),
1040
- };
1041
- }