@nordsym/apiclaw 1.3.12 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PRD-API-CHAINING.md +483 -0
- package/PRD-HARDEN-SHELL.md +278 -0
- package/README.md +72 -0
- package/convex/_generated/api.d.ts +4 -0
- package/convex/chains.ts +1095 -0
- package/convex/crons.ts +11 -0
- package/convex/logs.ts +107 -0
- package/convex/schema.ts +107 -0
- package/convex/spendAlerts.ts +442 -0
- package/convex/workspaces.ts +26 -0
- package/dist/chain-types.d.ts +187 -0
- package/dist/chain-types.d.ts.map +1 -0
- package/dist/chain-types.js +33 -0
- package/dist/chain-types.js.map +1 -0
- package/dist/chainExecutor.d.ts +122 -0
- package/dist/chainExecutor.d.ts.map +1 -0
- package/dist/chainExecutor.js +454 -0
- package/dist/chainExecutor.js.map +1 -0
- package/dist/chainResolver.d.ts +100 -0
- package/dist/chainResolver.d.ts.map +1 -0
- package/dist/chainResolver.js +519 -0
- package/dist/chainResolver.js.map +1 -0
- package/dist/chainResolver.test.d.ts +5 -0
- package/dist/chainResolver.test.d.ts.map +1 -0
- package/dist/chainResolver.test.js +201 -0
- package/dist/chainResolver.test.js.map +1 -0
- package/dist/execute.d.ts +5 -1
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +207 -118
- package/dist/execute.js.map +1 -1
- package/dist/index.js +382 -2
- package/dist/index.js.map +1 -1
- package/landing/package-lock.json +29 -5
- package/landing/package.json +2 -1
- package/landing/public/logos/chattgpt.svg +1 -0
- package/landing/public/logos/claude.svg +1 -0
- package/landing/public/logos/gemini.svg +1 -0
- package/landing/public/logos/grok.svg +1 -0
- package/landing/src/app/page.tsx +11 -0
- package/landing/src/app/security/page.tsx +381 -0
- package/landing/src/app/workspace/chains/page.tsx +520 -0
- package/landing/src/components/AITestimonials.tsx +195 -0
- package/landing/src/components/ChainStepDetail.tsx +310 -0
- package/landing/src/components/ChainTrace.tsx +261 -0
- package/landing/src/lib/stats.json +1 -1
- package/package.json +1 -1
- package/src/chain-types.ts +270 -0
- package/src/chainExecutor.ts +730 -0
- package/src/chainResolver.test.ts +246 -0
- package/src/chainResolver.ts +658 -0
- package/src/execute.ts +273 -114
- package/src/index.ts +423 -2
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { CheckCircle2, XCircle, Loader2, PauseCircle, SkipForward, Clock, DollarSign, Sparkles } from "lucide-react";
|
|
5
|
+
|
|
6
|
+
interface StepExecution {
|
|
7
|
+
_id: string;
|
|
8
|
+
stepId: string;
|
|
9
|
+
stepIndex: number;
|
|
10
|
+
status: "pending" | "running" | "completed" | "failed" | "skipped";
|
|
11
|
+
input?: any;
|
|
12
|
+
output?: any;
|
|
13
|
+
latencyMs?: number;
|
|
14
|
+
costCents?: number;
|
|
15
|
+
error?: {
|
|
16
|
+
code: string;
|
|
17
|
+
message: string;
|
|
18
|
+
retryCount?: number;
|
|
19
|
+
};
|
|
20
|
+
parallelGroup?: string;
|
|
21
|
+
createdAt: number;
|
|
22
|
+
startedAt?: number;
|
|
23
|
+
completedAt?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface ChainTraceProps {
|
|
27
|
+
chain: {
|
|
28
|
+
_id: string;
|
|
29
|
+
status: string;
|
|
30
|
+
totalCostCents: number;
|
|
31
|
+
totalLatencyMs: number;
|
|
32
|
+
startedAt?: number;
|
|
33
|
+
completedAt?: number;
|
|
34
|
+
steps?: any[];
|
|
35
|
+
};
|
|
36
|
+
executions: StepExecution[];
|
|
37
|
+
tokensSaved: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function ChainTrace({ chain, executions, tokensSaved }: ChainTraceProps) {
|
|
41
|
+
// Calculate timeline boundaries
|
|
42
|
+
const timelineBounds = useMemo(() => {
|
|
43
|
+
if (executions.length === 0) return { start: 0, end: 1000, duration: 1000 };
|
|
44
|
+
|
|
45
|
+
const startTimes = executions
|
|
46
|
+
.filter((e) => e.startedAt)
|
|
47
|
+
.map((e) => e.startedAt!);
|
|
48
|
+
const endTimes = executions
|
|
49
|
+
.filter((e) => e.completedAt)
|
|
50
|
+
.map((e) => e.completedAt!);
|
|
51
|
+
|
|
52
|
+
const start = startTimes.length > 0 ? Math.min(...startTimes) : chain.startedAt || Date.now();
|
|
53
|
+
const end = endTimes.length > 0 ? Math.max(...endTimes) : chain.completedAt || Date.now();
|
|
54
|
+
const duration = Math.max(end - start, 1);
|
|
55
|
+
|
|
56
|
+
return { start, end, duration };
|
|
57
|
+
}, [executions, chain]);
|
|
58
|
+
|
|
59
|
+
// Group parallel executions
|
|
60
|
+
const groupedExecutions = useMemo(() => {
|
|
61
|
+
const groups: { group: string | null; steps: StepExecution[] }[] = [];
|
|
62
|
+
let currentGroup: string | null = null;
|
|
63
|
+
let currentSteps: StepExecution[] = [];
|
|
64
|
+
|
|
65
|
+
executions.forEach((exec) => {
|
|
66
|
+
if (exec.parallelGroup !== currentGroup) {
|
|
67
|
+
if (currentSteps.length > 0) {
|
|
68
|
+
groups.push({ group: currentGroup, steps: currentSteps });
|
|
69
|
+
}
|
|
70
|
+
currentGroup = exec.parallelGroup || null;
|
|
71
|
+
currentSteps = [exec];
|
|
72
|
+
} else {
|
|
73
|
+
currentSteps.push(exec);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
if (currentSteps.length > 0) {
|
|
78
|
+
groups.push({ group: currentGroup, steps: currentSteps });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return groups;
|
|
82
|
+
}, [executions]);
|
|
83
|
+
|
|
84
|
+
const getStatusIcon = (status: string) => {
|
|
85
|
+
switch (status) {
|
|
86
|
+
case "completed":
|
|
87
|
+
return <CheckCircle2 className="w-3.5 h-3.5 text-green-500" />;
|
|
88
|
+
case "running":
|
|
89
|
+
return <Loader2 className="w-3.5 h-3.5 text-blue-500 animate-spin" />;
|
|
90
|
+
case "failed":
|
|
91
|
+
return <XCircle className="w-3.5 h-3.5 text-red-500" />;
|
|
92
|
+
case "skipped":
|
|
93
|
+
return <SkipForward className="w-3.5 h-3.5 text-gray-500" />;
|
|
94
|
+
case "paused":
|
|
95
|
+
return <PauseCircle className="w-3.5 h-3.5 text-yellow-500" />;
|
|
96
|
+
default:
|
|
97
|
+
return <Clock className="w-3.5 h-3.5 text-gray-500" />;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const getBarColor = (status: string) => {
|
|
102
|
+
switch (status) {
|
|
103
|
+
case "completed":
|
|
104
|
+
return "bg-green-500";
|
|
105
|
+
case "running":
|
|
106
|
+
return "bg-blue-500 animate-pulse";
|
|
107
|
+
case "failed":
|
|
108
|
+
return "bg-red-500";
|
|
109
|
+
case "skipped":
|
|
110
|
+
return "bg-gray-500";
|
|
111
|
+
case "paused":
|
|
112
|
+
return "bg-yellow-500";
|
|
113
|
+
default:
|
|
114
|
+
return "bg-gray-600";
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const formatDuration = (ms: number) => {
|
|
119
|
+
if (ms < 1000) return `${ms}ms`;
|
|
120
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const formatCost = (cents: number) => {
|
|
124
|
+
if (cents === 0) return "$0.00";
|
|
125
|
+
return `$${(cents / 100).toFixed(2)}`;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const calculateBarPosition = (step: StepExecution) => {
|
|
129
|
+
const start = step.startedAt || timelineBounds.start;
|
|
130
|
+
const end = step.completedAt || (step.startedAt ? step.startedAt + (step.latencyMs || 0) : timelineBounds.end);
|
|
131
|
+
|
|
132
|
+
const left = ((start - timelineBounds.start) / timelineBounds.duration) * 100;
|
|
133
|
+
const width = ((end - start) / timelineBounds.duration) * 100;
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
left: `${Math.max(0, left)}%`,
|
|
137
|
+
width: `${Math.max(2, Math.min(100 - left, width))}%`,
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// Get provider info from step definition
|
|
142
|
+
const getStepProvider = (stepId: string) => {
|
|
143
|
+
const stepDef = chain.steps?.find((s: any) => s.id === stepId);
|
|
144
|
+
return stepDef?.provider || "unknown";
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<div className="bg-black/30 rounded-xl border border-white/10 overflow-hidden">
|
|
149
|
+
{/* Header */}
|
|
150
|
+
<div className="px-4 py-3 border-b border-white/10 flex items-center justify-between">
|
|
151
|
+
<div className="flex items-center gap-2">
|
|
152
|
+
<span className="text-sm font-medium">Chain:</span>
|
|
153
|
+
<code className="text-xs text-white/60 font-mono">{chain._id.slice(0, 16)}...</code>
|
|
154
|
+
</div>
|
|
155
|
+
<div className="flex items-center gap-4 text-sm text-white/60">
|
|
156
|
+
<span className="flex items-center gap-1">
|
|
157
|
+
<Clock className="w-3.5 h-3.5" />
|
|
158
|
+
Total: {formatDuration(chain.totalLatencyMs)}
|
|
159
|
+
</span>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
{/* Timeline */}
|
|
164
|
+
<div className="p-4 space-y-2">
|
|
165
|
+
{groupedExecutions.map(({ group, steps }, groupIndex) => (
|
|
166
|
+
<div key={group || groupIndex}>
|
|
167
|
+
{/* Parallel group indicator */}
|
|
168
|
+
{group && steps.length > 1 && (
|
|
169
|
+
<div className="text-xs text-white/40 mb-1 flex items-center gap-1">
|
|
170
|
+
<span className="px-1.5 py-0.5 bg-white/10 rounded">Parallel</span>
|
|
171
|
+
</div>
|
|
172
|
+
)}
|
|
173
|
+
|
|
174
|
+
{/* Steps in group */}
|
|
175
|
+
<div className={group && steps.length > 1 ? "pl-4 border-l-2 border-white/10 space-y-2" : "space-y-2"}>
|
|
176
|
+
{steps.map((step) => {
|
|
177
|
+
const position = calculateBarPosition(step);
|
|
178
|
+
return (
|
|
179
|
+
<div key={step._id} className="flex items-center gap-3">
|
|
180
|
+
{/* Step name */}
|
|
181
|
+
<div className="w-24 flex-shrink-0 flex items-center gap-2">
|
|
182
|
+
{getStatusIcon(step.status)}
|
|
183
|
+
<span className="text-sm font-mono truncate" title={step.stepId}>
|
|
184
|
+
{step.stepId}
|
|
185
|
+
</span>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
{/* Gantt bar */}
|
|
189
|
+
<div className="flex-1 h-6 bg-white/5 rounded relative overflow-hidden">
|
|
190
|
+
<div
|
|
191
|
+
className={`absolute top-0 h-full rounded ${getBarColor(step.status)}`}
|
|
192
|
+
style={{
|
|
193
|
+
left: position.left,
|
|
194
|
+
width: position.width,
|
|
195
|
+
}}
|
|
196
|
+
/>
|
|
197
|
+
{/* Time markers - simplified */}
|
|
198
|
+
<div className="absolute inset-0 flex items-center px-2">
|
|
199
|
+
<span
|
|
200
|
+
className="text-xs font-mono text-white/80 drop-shadow-sm"
|
|
201
|
+
style={{
|
|
202
|
+
marginLeft: position.left,
|
|
203
|
+
}}
|
|
204
|
+
>
|
|
205
|
+
{formatDuration(step.latencyMs || 0)}
|
|
206
|
+
</span>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
{/* Cost */}
|
|
211
|
+
<div className="w-16 text-right text-xs text-white/60">
|
|
212
|
+
{formatCost(step.costCents || 0)}
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
);
|
|
216
|
+
})}
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
))}
|
|
220
|
+
</div>
|
|
221
|
+
|
|
222
|
+
{/* Footer */}
|
|
223
|
+
<div className="px-4 py-3 border-t border-white/10 flex items-center justify-between text-sm">
|
|
224
|
+
<div className="flex items-center gap-4">
|
|
225
|
+
<span className="flex items-center gap-1 text-white/60">
|
|
226
|
+
<DollarSign className="w-3.5 h-3.5" />
|
|
227
|
+
Total Cost: <span className="text-white font-medium">{formatCost(chain.totalCostCents)}</span>
|
|
228
|
+
</span>
|
|
229
|
+
</div>
|
|
230
|
+
<div className="flex items-center gap-1 text-white/60">
|
|
231
|
+
<Sparkles className="w-3.5 h-3.5 text-yellow-500" />
|
|
232
|
+
<span>Tokens Saved: <span className="text-yellow-500 font-medium">~{tokensSaved.toLocaleString()}</span></span>
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
|
|
236
|
+
{/* Legend */}
|
|
237
|
+
<div className="px-4 py-2 border-t border-white/5 flex items-center gap-4 text-xs text-white/40">
|
|
238
|
+
<div className="flex items-center gap-1">
|
|
239
|
+
<div className="w-3 h-3 rounded bg-green-500" />
|
|
240
|
+
<span>Completed</span>
|
|
241
|
+
</div>
|
|
242
|
+
<div className="flex items-center gap-1">
|
|
243
|
+
<div className="w-3 h-3 rounded bg-blue-500" />
|
|
244
|
+
<span>Running</span>
|
|
245
|
+
</div>
|
|
246
|
+
<div className="flex items-center gap-1">
|
|
247
|
+
<div className="w-3 h-3 rounded bg-red-500" />
|
|
248
|
+
<span>Failed</span>
|
|
249
|
+
</div>
|
|
250
|
+
<div className="flex items-center gap-1">
|
|
251
|
+
<div className="w-3 h-3 rounded bg-yellow-500" />
|
|
252
|
+
<span>Paused</span>
|
|
253
|
+
</div>
|
|
254
|
+
<div className="flex items-center gap-1">
|
|
255
|
+
<div className="w-3 h-3 rounded bg-gray-500" />
|
|
256
|
+
<span>Skipped</span>
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
);
|
|
261
|
+
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chain Execution Types for APIClaw
|
|
3
|
+
*
|
|
4
|
+
* Enables multi-step API orchestration with:
|
|
5
|
+
* - Sequential execution
|
|
6
|
+
* - Parallel execution
|
|
7
|
+
* - Conditional branching
|
|
8
|
+
* - Loops (forEach, map-reduce)
|
|
9
|
+
* - Error handling & retry
|
|
10
|
+
* - Cross-step references ($stepId.property)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// ============================================
|
|
14
|
+
// CHAIN STEP TYPES
|
|
15
|
+
// ============================================
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Base step configuration
|
|
19
|
+
*/
|
|
20
|
+
export interface ChainStepBase {
|
|
21
|
+
id: string;
|
|
22
|
+
provider: string;
|
|
23
|
+
action: string;
|
|
24
|
+
params?: Record<string, any>;
|
|
25
|
+
onError?: ErrorHandler;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Parallel execution step - runs multiple steps concurrently
|
|
30
|
+
*/
|
|
31
|
+
export interface ParallelStep {
|
|
32
|
+
parallel: ChainStepBase[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Conditional step - executes based on condition
|
|
37
|
+
*/
|
|
38
|
+
export interface ConditionalStep {
|
|
39
|
+
if: string; // Expression like "$step1.success === true"
|
|
40
|
+
then: ChainStepBase | ChainStep;
|
|
41
|
+
else?: ChainStepBase | ChainStep;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Loop step - iterates over array
|
|
46
|
+
*/
|
|
47
|
+
export interface ForEachStep {
|
|
48
|
+
forEach: string; // Reference like "$step1.results"
|
|
49
|
+
as: string; // Variable name for current item
|
|
50
|
+
do: ChainStepBase;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Map step - transforms array
|
|
55
|
+
*/
|
|
56
|
+
export interface MapStep {
|
|
57
|
+
map: {
|
|
58
|
+
input: string;
|
|
59
|
+
fn: ChainStepBase;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Reduce step - aggregates results
|
|
65
|
+
*/
|
|
66
|
+
export interface ReduceStep {
|
|
67
|
+
reduce: {
|
|
68
|
+
input: string;
|
|
69
|
+
fn: ChainStepBase;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Union type for all step variants
|
|
75
|
+
*/
|
|
76
|
+
export type ChainStep =
|
|
77
|
+
| ChainStepBase
|
|
78
|
+
| ParallelStep
|
|
79
|
+
| ConditionalStep
|
|
80
|
+
| ForEachStep
|
|
81
|
+
| MapStep
|
|
82
|
+
| ReduceStep;
|
|
83
|
+
|
|
84
|
+
// ============================================
|
|
85
|
+
// ERROR HANDLING
|
|
86
|
+
// ============================================
|
|
87
|
+
|
|
88
|
+
export interface RetryConfig {
|
|
89
|
+
attempts?: number;
|
|
90
|
+
maxAttempts?: number;
|
|
91
|
+
backoff?: number[] | 'exponential' | 'linear';
|
|
92
|
+
backoffMs?: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface ErrorHandler {
|
|
96
|
+
retry?: RetryConfig;
|
|
97
|
+
fallback?: ChainStepBase;
|
|
98
|
+
abort?: boolean;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export type ErrorPolicy = 'fail-fast' | 'best-effort' | 'transactional';
|
|
102
|
+
|
|
103
|
+
export interface RollbackAction {
|
|
104
|
+
if: string;
|
|
105
|
+
do: ChainStepBase;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface ChainErrorPolicy {
|
|
109
|
+
mode: ErrorPolicy;
|
|
110
|
+
rollback?: RollbackAction[];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ============================================
|
|
114
|
+
// CHAIN CONFIGURATION
|
|
115
|
+
// ============================================
|
|
116
|
+
|
|
117
|
+
export interface ChainLimits {
|
|
118
|
+
maxSteps?: number;
|
|
119
|
+
maxParallel?: number;
|
|
120
|
+
maxCost?: { cents: number };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface ChainOptions {
|
|
124
|
+
continueOnError?: boolean;
|
|
125
|
+
timeout?: number; // Max execution time in ms
|
|
126
|
+
async?: boolean; // Return immediately with chainId
|
|
127
|
+
webhook?: string; // URL to call on completion
|
|
128
|
+
errorPolicy?: ChainErrorPolicy;
|
|
129
|
+
limits?: ChainLimits;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ============================================
|
|
133
|
+
// CHAIN EXECUTION REQUEST
|
|
134
|
+
// ============================================
|
|
135
|
+
|
|
136
|
+
export interface ChainRequest extends ChainOptions {
|
|
137
|
+
chain: ChainStep[];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ============================================
|
|
141
|
+
// STEP RESULT
|
|
142
|
+
// ============================================
|
|
143
|
+
|
|
144
|
+
export type StepStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped';
|
|
145
|
+
|
|
146
|
+
export interface StepResult {
|
|
147
|
+
id: string;
|
|
148
|
+
status: StepStatus;
|
|
149
|
+
result?: any;
|
|
150
|
+
error?: string;
|
|
151
|
+
errorCode?: string;
|
|
152
|
+
latencyMs: number;
|
|
153
|
+
cost?: { cents: number };
|
|
154
|
+
retryAttempts?: number;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ============================================
|
|
158
|
+
// CHAIN RESPONSE
|
|
159
|
+
// ============================================
|
|
160
|
+
|
|
161
|
+
export interface ChainSuccessResponse {
|
|
162
|
+
success: true;
|
|
163
|
+
chainId: string;
|
|
164
|
+
steps: StepResult[];
|
|
165
|
+
finalResult: any;
|
|
166
|
+
totalLatencyMs: number;
|
|
167
|
+
totalCost: { cents: number };
|
|
168
|
+
tokensSaved: number;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface ChainErrorResponse {
|
|
172
|
+
success: false;
|
|
173
|
+
chainId: string;
|
|
174
|
+
completedSteps: string[];
|
|
175
|
+
failedStep: {
|
|
176
|
+
id: string;
|
|
177
|
+
error: string;
|
|
178
|
+
code?: string;
|
|
179
|
+
retryAfter?: number;
|
|
180
|
+
};
|
|
181
|
+
partialResults: Record<string, any>;
|
|
182
|
+
canResume: boolean;
|
|
183
|
+
resumeToken?: string;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export type ChainResponse = ChainSuccessResponse | ChainErrorResponse;
|
|
187
|
+
|
|
188
|
+
// ============================================
|
|
189
|
+
// ASYNC CHAIN STATUS
|
|
190
|
+
// ============================================
|
|
191
|
+
|
|
192
|
+
export interface AsyncChainResponse {
|
|
193
|
+
chainId: string;
|
|
194
|
+
status: 'running' | 'completed' | 'failed';
|
|
195
|
+
estimatedCompletion?: string;
|
|
196
|
+
statusUrl: string;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export interface ChainStatusResponse {
|
|
200
|
+
chainId: string;
|
|
201
|
+
status: 'pending' | 'running' | 'completed' | 'failed';
|
|
202
|
+
progress: {
|
|
203
|
+
completedSteps: number;
|
|
204
|
+
totalSteps: number;
|
|
205
|
+
currentStep?: string;
|
|
206
|
+
};
|
|
207
|
+
startedAt: string;
|
|
208
|
+
completedAt?: string;
|
|
209
|
+
result?: ChainResponse;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ============================================
|
|
213
|
+
// RESUME CHAIN
|
|
214
|
+
// ============================================
|
|
215
|
+
|
|
216
|
+
export interface ResumeChainRequest {
|
|
217
|
+
resumeToken: string;
|
|
218
|
+
overrides?: Record<string, Record<string, any>>; // stepId -> params overrides
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ============================================
|
|
222
|
+
// STORED CHAIN TEMPLATES
|
|
223
|
+
// ============================================
|
|
224
|
+
|
|
225
|
+
export interface ChainInput {
|
|
226
|
+
type: 'string' | 'number' | 'boolean' | 'object' | 'array';
|
|
227
|
+
required?: boolean;
|
|
228
|
+
default?: any;
|
|
229
|
+
description?: string;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export interface ChainTemplate {
|
|
233
|
+
name: string;
|
|
234
|
+
description?: string;
|
|
235
|
+
inputs: Record<string, ChainInput>;
|
|
236
|
+
chain: ChainStep[];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export interface UseChainRequest {
|
|
240
|
+
useChain: string;
|
|
241
|
+
inputs: Record<string, any>;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ============================================
|
|
245
|
+
// TYPE GUARDS
|
|
246
|
+
// ============================================
|
|
247
|
+
|
|
248
|
+
export function isParallelStep(step: ChainStep): step is ParallelStep {
|
|
249
|
+
return 'parallel' in step;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function isConditionalStep(step: ChainStep): step is ConditionalStep {
|
|
253
|
+
return 'if' in step && 'then' in step;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function isForEachStep(step: ChainStep): step is ForEachStep {
|
|
257
|
+
return 'forEach' in step && 'do' in step;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function isMapStep(step: ChainStep): step is MapStep {
|
|
261
|
+
return 'map' in step;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function isReduceStep(step: ChainStep): step is ReduceStep {
|
|
265
|
+
return 'reduce' in step;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function isBaseStep(step: ChainStep): step is ChainStepBase {
|
|
269
|
+
return 'provider' in step && 'action' in step;
|
|
270
|
+
}
|