@grimoirelabs/core 0.1.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/dist/builders/expressions.d.ts +33 -0
- package/dist/builders/expressions.d.ts.map +1 -0
- package/dist/builders/expressions.js +57 -0
- package/dist/builders/expressions.js.map +1 -0
- package/dist/builders/index.d.ts +44 -0
- package/dist/builders/index.d.ts.map +1 -0
- package/dist/builders/index.js +32 -0
- package/dist/builders/index.js.map +1 -0
- package/dist/builders/spell-builder.d.ts +124 -0
- package/dist/builders/spell-builder.d.ts.map +1 -0
- package/dist/builders/spell-builder.js +299 -0
- package/dist/builders/spell-builder.js.map +1 -0
- package/dist/builders/step-builder.d.ts +212 -0
- package/dist/builders/step-builder.d.ts.map +1 -0
- package/dist/builders/step-builder.js +499 -0
- package/dist/builders/step-builder.js.map +1 -0
- package/dist/compiler/expression-parser.d.ts +14 -0
- package/dist/compiler/expression-parser.d.ts.map +1 -0
- package/dist/compiler/expression-parser.js +460 -0
- package/dist/compiler/expression-parser.js.map +1 -0
- package/dist/compiler/grimoire/ast.d.ts +450 -0
- package/dist/compiler/grimoire/ast.d.ts.map +1 -0
- package/dist/compiler/grimoire/ast.js +19 -0
- package/dist/compiler/grimoire/ast.js.map +1 -0
- package/dist/compiler/grimoire/errors.d.ts +65 -0
- package/dist/compiler/grimoire/errors.d.ts.map +1 -0
- package/dist/compiler/grimoire/errors.js +86 -0
- package/dist/compiler/grimoire/errors.js.map +1 -0
- package/dist/compiler/grimoire/index.d.ts +24 -0
- package/dist/compiler/grimoire/index.d.ts.map +1 -0
- package/dist/compiler/grimoire/index.js +63 -0
- package/dist/compiler/grimoire/index.js.map +1 -0
- package/dist/compiler/grimoire/parser.d.ts +135 -0
- package/dist/compiler/grimoire/parser.d.ts.map +1 -0
- package/dist/compiler/grimoire/parser.js +2148 -0
- package/dist/compiler/grimoire/parser.js.map +1 -0
- package/dist/compiler/grimoire/tokenizer.d.ts +59 -0
- package/dist/compiler/grimoire/tokenizer.d.ts.map +1 -0
- package/dist/compiler/grimoire/tokenizer.js +509 -0
- package/dist/compiler/grimoire/tokenizer.js.map +1 -0
- package/dist/compiler/grimoire/transformer.d.ts +71 -0
- package/dist/compiler/grimoire/transformer.d.ts.map +1 -0
- package/dist/compiler/grimoire/transformer.js +1011 -0
- package/dist/compiler/grimoire/transformer.js.map +1 -0
- package/dist/compiler/index.d.ts +45 -0
- package/dist/compiler/index.d.ts.map +1 -0
- package/dist/compiler/index.js +97 -0
- package/dist/compiler/index.js.map +1 -0
- package/dist/compiler/ir-generator.d.ts +16 -0
- package/dist/compiler/ir-generator.d.ts.map +1 -0
- package/dist/compiler/ir-generator.js +997 -0
- package/dist/compiler/ir-generator.js.map +1 -0
- package/dist/compiler/validator.d.ts +15 -0
- package/dist/compiler/validator.d.ts.map +1 -0
- package/dist/compiler/validator.js +401 -0
- package/dist/compiler/validator.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/circuit-breaker.d.ts +59 -0
- package/dist/runtime/circuit-breaker.d.ts.map +1 -0
- package/dist/runtime/circuit-breaker.js +155 -0
- package/dist/runtime/circuit-breaker.js.map +1 -0
- package/dist/runtime/context.d.ts +92 -0
- package/dist/runtime/context.d.ts.map +1 -0
- package/dist/runtime/context.js +219 -0
- package/dist/runtime/context.js.map +1 -0
- package/dist/runtime/error-classifier.d.ts +16 -0
- package/dist/runtime/error-classifier.d.ts.map +1 -0
- package/dist/runtime/error-classifier.js +38 -0
- package/dist/runtime/error-classifier.js.map +1 -0
- package/dist/runtime/expression-evaluator.d.ts +36 -0
- package/dist/runtime/expression-evaluator.d.ts.map +1 -0
- package/dist/runtime/expression-evaluator.js +391 -0
- package/dist/runtime/expression-evaluator.js.map +1 -0
- package/dist/runtime/index.d.ts +12 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +11 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/interpreter.d.ts +59 -0
- package/dist/runtime/interpreter.d.ts.map +1 -0
- package/dist/runtime/interpreter.js +414 -0
- package/dist/runtime/interpreter.js.map +1 -0
- package/dist/runtime/skills/registry.d.ts +11 -0
- package/dist/runtime/skills/registry.d.ts.map +1 -0
- package/dist/runtime/skills/registry.js +73 -0
- package/dist/runtime/skills/registry.js.map +1 -0
- package/dist/runtime/sqlite-state-store.d.ts +28 -0
- package/dist/runtime/sqlite-state-store.d.ts.map +1 -0
- package/dist/runtime/sqlite-state-store.js +180 -0
- package/dist/runtime/sqlite-state-store.js.map +1 -0
- package/dist/runtime/state-store.d.ts +52 -0
- package/dist/runtime/state-store.d.ts.map +1 -0
- package/dist/runtime/state-store.js +32 -0
- package/dist/runtime/state-store.js.map +1 -0
- package/dist/runtime/steps/action.d.ts +17 -0
- package/dist/runtime/steps/action.d.ts.map +1 -0
- package/dist/runtime/steps/action.js +430 -0
- package/dist/runtime/steps/action.js.map +1 -0
- package/dist/runtime/steps/advisory.d.ts +28 -0
- package/dist/runtime/steps/advisory.d.ts.map +1 -0
- package/dist/runtime/steps/advisory.js +209 -0
- package/dist/runtime/steps/advisory.js.map +1 -0
- package/dist/runtime/steps/compute.d.ts +9 -0
- package/dist/runtime/steps/compute.d.ts.map +1 -0
- package/dist/runtime/steps/compute.js +74 -0
- package/dist/runtime/steps/compute.js.map +1 -0
- package/dist/runtime/steps/conditional.d.ts +14 -0
- package/dist/runtime/steps/conditional.d.ts.map +1 -0
- package/dist/runtime/steps/conditional.js +58 -0
- package/dist/runtime/steps/conditional.js.map +1 -0
- package/dist/runtime/steps/emit.d.ts +9 -0
- package/dist/runtime/steps/emit.d.ts.map +1 -0
- package/dist/runtime/steps/emit.js +70 -0
- package/dist/runtime/steps/emit.js.map +1 -0
- package/dist/runtime/steps/halt.d.ts +9 -0
- package/dist/runtime/steps/halt.d.ts.map +1 -0
- package/dist/runtime/steps/halt.js +19 -0
- package/dist/runtime/steps/halt.js.map +1 -0
- package/dist/runtime/steps/loop.d.ts +14 -0
- package/dist/runtime/steps/loop.d.ts.map +1 -0
- package/dist/runtime/steps/loop.js +109 -0
- package/dist/runtime/steps/loop.js.map +1 -0
- package/dist/runtime/steps/parallel.d.ts +9 -0
- package/dist/runtime/steps/parallel.d.ts.map +1 -0
- package/dist/runtime/steps/parallel.js +87 -0
- package/dist/runtime/steps/parallel.js.map +1 -0
- package/dist/runtime/steps/pipeline.d.ts +9 -0
- package/dist/runtime/steps/pipeline.d.ts.map +1 -0
- package/dist/runtime/steps/pipeline.js +125 -0
- package/dist/runtime/steps/pipeline.js.map +1 -0
- package/dist/runtime/steps/try.d.ts +13 -0
- package/dist/runtime/steps/try.d.ts.map +1 -0
- package/dist/runtime/steps/try.js +222 -0
- package/dist/runtime/steps/try.js.map +1 -0
- package/dist/runtime/steps/wait.d.ts +9 -0
- package/dist/runtime/steps/wait.d.ts.map +1 -0
- package/dist/runtime/steps/wait.js +38 -0
- package/dist/runtime/steps/wait.js.map +1 -0
- package/dist/types/actions.d.ts +162 -0
- package/dist/types/actions.d.ts.map +1 -0
- package/dist/types/actions.js +5 -0
- package/dist/types/actions.js.map +1 -0
- package/dist/types/execution.d.ts +276 -0
- package/dist/types/execution.d.ts.map +1 -0
- package/dist/types/execution.js +5 -0
- package/dist/types/execution.js.map +1 -0
- package/dist/types/expressions.d.ts +100 -0
- package/dist/types/expressions.d.ts.map +1 -0
- package/dist/types/expressions.js +48 -0
- package/dist/types/expressions.js.map +1 -0
- package/dist/types/index.d.ts +14 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/ir.d.ts +187 -0
- package/dist/types/ir.d.ts.map +1 -0
- package/dist/types/ir.js +5 -0
- package/dist/types/ir.js.map +1 -0
- package/dist/types/policy.d.ts +123 -0
- package/dist/types/policy.d.ts.map +1 -0
- package/dist/types/policy.js +5 -0
- package/dist/types/policy.js.map +1 -0
- package/dist/types/primitives.d.ts +76 -0
- package/dist/types/primitives.d.ts.map +1 -0
- package/dist/types/primitives.js +10 -0
- package/dist/types/primitives.js.map +1 -0
- package/dist/types/steps.d.ts +226 -0
- package/dist/types/steps.d.ts.map +1 -0
- package/dist/types/steps.js +5 -0
- package/dist/types/steps.js.map +1 -0
- package/dist/venues/index.d.ts +6 -0
- package/dist/venues/index.d.ts.map +1 -0
- package/dist/venues/index.js +26 -0
- package/dist/venues/index.js.map +1 -0
- package/dist/venues/types.d.ts +40 -0
- package/dist/venues/types.d.ts.map +1 -0
- package/dist/venues/types.js +5 -0
- package/dist/venues/types.js.map +1 -0
- package/dist/wallet/executor.d.ts +109 -0
- package/dist/wallet/executor.d.ts.map +1 -0
- package/dist/wallet/executor.js +354 -0
- package/dist/wallet/executor.js.map +1 -0
- package/dist/wallet/index.d.ts +14 -0
- package/dist/wallet/index.d.ts.map +1 -0
- package/dist/wallet/index.js +13 -0
- package/dist/wallet/index.js.map +1 -0
- package/dist/wallet/keystore.d.ts +44 -0
- package/dist/wallet/keystore.d.ts.map +1 -0
- package/dist/wallet/keystore.js +296 -0
- package/dist/wallet/keystore.js.map +1 -0
- package/dist/wallet/provider.d.ts +111 -0
- package/dist/wallet/provider.d.ts.map +1 -0
- package/dist/wallet/provider.js +309 -0
- package/dist/wallet/provider.js.map +1 -0
- package/dist/wallet/tx-builder.d.ts +85 -0
- package/dist/wallet/tx-builder.d.ts.map +1 -0
- package/dist/wallet/tx-builder.js +290 -0
- package/dist/wallet/tx-builder.js.map +1 -0
- package/dist/wallet/types.d.ts +116 -0
- package/dist/wallet/types.d.ts.map +1 -0
- package/dist/wallet/types.js +86 -0
- package/dist/wallet/types.js.map +1 -0
- package/package.json +39 -0
|
@@ -0,0 +1,997 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IR Generator
|
|
3
|
+
* Transforms SpellSource AST into SpellIR
|
|
4
|
+
*/
|
|
5
|
+
import { parseExpression } from "./expression-parser.js";
|
|
6
|
+
/**
|
|
7
|
+
* Generate IR from parsed SpellSource
|
|
8
|
+
*/
|
|
9
|
+
export function generateIR(source) {
|
|
10
|
+
const errors = [];
|
|
11
|
+
const warnings = [];
|
|
12
|
+
// Generate unique ID and hash
|
|
13
|
+
const id = source.spell;
|
|
14
|
+
const hash = generateHash(JSON.stringify(source));
|
|
15
|
+
// Transform venues
|
|
16
|
+
const aliases = [];
|
|
17
|
+
if (source.venues) {
|
|
18
|
+
for (const [alias, venue] of Object.entries(source.venues)) {
|
|
19
|
+
aliases.push({
|
|
20
|
+
alias,
|
|
21
|
+
chain: venue.chain,
|
|
22
|
+
address: venue.address,
|
|
23
|
+
label: venue.label,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Transform assets
|
|
28
|
+
const assets = [];
|
|
29
|
+
if (source.assets) {
|
|
30
|
+
for (const [symbol, asset] of Object.entries(source.assets)) {
|
|
31
|
+
assets.push({
|
|
32
|
+
symbol,
|
|
33
|
+
chain: asset.chain,
|
|
34
|
+
address: asset.address,
|
|
35
|
+
decimals: asset.decimals,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Transform params
|
|
40
|
+
const params = [];
|
|
41
|
+
if (source.params) {
|
|
42
|
+
for (const [name, value] of Object.entries(source.params)) {
|
|
43
|
+
if (typeof value === "object" && value !== null && "type" in value) {
|
|
44
|
+
const extended = value;
|
|
45
|
+
params.push({
|
|
46
|
+
name,
|
|
47
|
+
type: (extended.type ?? "number"),
|
|
48
|
+
asset: extended.asset,
|
|
49
|
+
default: extended.default,
|
|
50
|
+
min: extended.min,
|
|
51
|
+
max: extended.max,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
// Simple form - infer type from value
|
|
56
|
+
params.push({
|
|
57
|
+
name,
|
|
58
|
+
type: inferType(value),
|
|
59
|
+
default: value,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Transform state
|
|
65
|
+
const state = {
|
|
66
|
+
persistent: {},
|
|
67
|
+
ephemeral: {},
|
|
68
|
+
};
|
|
69
|
+
if (source.state?.persistent) {
|
|
70
|
+
for (const [key, value] of Object.entries(source.state.persistent)) {
|
|
71
|
+
state.persistent[key] = { key, initialValue: value };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (source.state?.ephemeral) {
|
|
75
|
+
for (const [key, value] of Object.entries(source.state.ephemeral)) {
|
|
76
|
+
state.ephemeral[key] = { key, initialValue: value };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Transform skills
|
|
80
|
+
const skills = [];
|
|
81
|
+
if (source.skills) {
|
|
82
|
+
for (const [name, skill] of Object.entries(source.skills)) {
|
|
83
|
+
skills.push({
|
|
84
|
+
name,
|
|
85
|
+
type: skill.type,
|
|
86
|
+
adapters: skill.adapters,
|
|
87
|
+
defaultConstraints: skill.default_constraints
|
|
88
|
+
? { maxSlippage: skill.default_constraints.max_slippage }
|
|
89
|
+
: undefined,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Transform advisors
|
|
94
|
+
const advisors = [];
|
|
95
|
+
if (source.advisors) {
|
|
96
|
+
for (const [name, advisor] of Object.entries(source.advisors)) {
|
|
97
|
+
advisors.push({
|
|
98
|
+
name,
|
|
99
|
+
model: advisor.model,
|
|
100
|
+
scope: "read-only",
|
|
101
|
+
systemPrompt: advisor.system_prompt,
|
|
102
|
+
skills: advisor.skills,
|
|
103
|
+
allowedTools: advisor.allowed_tools,
|
|
104
|
+
mcp: advisor.mcp,
|
|
105
|
+
defaultTimeout: advisor.timeout,
|
|
106
|
+
defaultFallback: advisor.fallback,
|
|
107
|
+
rateLimit: advisor.rate_limit
|
|
108
|
+
? {
|
|
109
|
+
maxCallsPerRun: advisor.rate_limit.max_per_run ?? 10,
|
|
110
|
+
maxCallsPerHour: advisor.rate_limit.max_per_hour ?? 100,
|
|
111
|
+
}
|
|
112
|
+
: undefined,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Transform triggers
|
|
117
|
+
const triggers = [];
|
|
118
|
+
if (source.trigger) {
|
|
119
|
+
const trigger = transformTrigger(source.trigger);
|
|
120
|
+
if (trigger) {
|
|
121
|
+
triggers.push(trigger);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
// Default to manual trigger
|
|
126
|
+
triggers.push({ type: "manual" });
|
|
127
|
+
}
|
|
128
|
+
// Transform steps and build source map
|
|
129
|
+
const steps = [];
|
|
130
|
+
const stepIds = new Set();
|
|
131
|
+
const sourceMap = {};
|
|
132
|
+
if (source.steps) {
|
|
133
|
+
for (const rawStep of source.steps) {
|
|
134
|
+
try {
|
|
135
|
+
const step = transformStep(rawStep, stepIds, errors);
|
|
136
|
+
if (step) {
|
|
137
|
+
steps.push(step);
|
|
138
|
+
stepIds.add(step.id);
|
|
139
|
+
// Extract source location from transformer metadata
|
|
140
|
+
const loc = rawStep._sourceLocation;
|
|
141
|
+
if (loc) {
|
|
142
|
+
sourceMap[step.id] = { line: loc.line, column: loc.column };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (e) {
|
|
147
|
+
errors.push({
|
|
148
|
+
code: "STEP_TRANSFORM_ERROR",
|
|
149
|
+
message: `Failed to transform step: ${e.message}`,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Transform guards
|
|
155
|
+
const guards = [];
|
|
156
|
+
if (source.guards) {
|
|
157
|
+
for (const rawGuard of source.guards) {
|
|
158
|
+
try {
|
|
159
|
+
const guard = transformGuard(rawGuard, errors);
|
|
160
|
+
if (guard) {
|
|
161
|
+
guards.push(guard);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch (e) {
|
|
165
|
+
errors.push({
|
|
166
|
+
code: "GUARD_TRANSFORM_ERROR",
|
|
167
|
+
message: `Failed to transform guard: ${e.message}`,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Auto-add "default" advisor if referenced by advisory steps but not declared
|
|
173
|
+
const advisorNames = new Set(advisors.map((a) => a.name));
|
|
174
|
+
const referencedAdvisors = new Set();
|
|
175
|
+
for (const step of steps) {
|
|
176
|
+
if (step.kind === "advisory" && !advisorNames.has(step.advisor)) {
|
|
177
|
+
referencedAdvisors.add(step.advisor);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
for (const name of referencedAdvisors) {
|
|
181
|
+
advisors.push({
|
|
182
|
+
name,
|
|
183
|
+
model: "sonnet",
|
|
184
|
+
scope: "read-only",
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
if (errors.length > 0) {
|
|
188
|
+
return { success: false, errors, warnings };
|
|
189
|
+
}
|
|
190
|
+
const ir = {
|
|
191
|
+
id,
|
|
192
|
+
version: source.version,
|
|
193
|
+
meta: {
|
|
194
|
+
name: source.spell,
|
|
195
|
+
description: source.description,
|
|
196
|
+
created: Date.now(),
|
|
197
|
+
hash,
|
|
198
|
+
},
|
|
199
|
+
aliases,
|
|
200
|
+
assets,
|
|
201
|
+
skills,
|
|
202
|
+
advisors,
|
|
203
|
+
params,
|
|
204
|
+
state,
|
|
205
|
+
steps,
|
|
206
|
+
guards,
|
|
207
|
+
triggers,
|
|
208
|
+
sourceMap: Object.keys(sourceMap).length > 0 ? sourceMap : undefined,
|
|
209
|
+
};
|
|
210
|
+
return { success: true, ir, errors, warnings };
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Generate a simple hash for content addressing
|
|
214
|
+
*/
|
|
215
|
+
function generateHash(content) {
|
|
216
|
+
let hash = 0;
|
|
217
|
+
for (let i = 0; i < content.length; i++) {
|
|
218
|
+
const char = content.charCodeAt(i);
|
|
219
|
+
hash = (hash << 5) - hash + char;
|
|
220
|
+
hash = hash & hash;
|
|
221
|
+
}
|
|
222
|
+
return Math.abs(hash).toString(16).padStart(8, "0");
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Infer parameter type from value
|
|
226
|
+
*/
|
|
227
|
+
function inferType(value) {
|
|
228
|
+
if (typeof value === "boolean")
|
|
229
|
+
return "bool";
|
|
230
|
+
if (typeof value === "number")
|
|
231
|
+
return "number";
|
|
232
|
+
if (typeof value === "string") {
|
|
233
|
+
if (value.startsWith("0x") && value.length === 42)
|
|
234
|
+
return "address";
|
|
235
|
+
return "string";
|
|
236
|
+
}
|
|
237
|
+
return "string";
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Transform trigger
|
|
241
|
+
*/
|
|
242
|
+
function transformTrigger(raw) {
|
|
243
|
+
if ("manual" in raw && raw.manual) {
|
|
244
|
+
return { type: "manual" };
|
|
245
|
+
}
|
|
246
|
+
if ("schedule" in raw) {
|
|
247
|
+
return { type: "schedule", cron: raw.schedule };
|
|
248
|
+
}
|
|
249
|
+
if ("condition" in raw) {
|
|
250
|
+
return {
|
|
251
|
+
type: "condition",
|
|
252
|
+
expression: raw.condition,
|
|
253
|
+
pollInterval: raw.poll_interval ?? 60,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
if ("event" in raw) {
|
|
257
|
+
return {
|
|
258
|
+
type: "event",
|
|
259
|
+
event: raw.event,
|
|
260
|
+
filter: raw.filter,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
if ("any" in raw && Array.isArray(raw.any)) {
|
|
264
|
+
const triggers = [];
|
|
265
|
+
for (const t of raw.any) {
|
|
266
|
+
const transformed = transformTrigger(t);
|
|
267
|
+
if (transformed) {
|
|
268
|
+
triggers.push(transformed);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return { type: "any", triggers };
|
|
272
|
+
}
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Transform a raw step object into a Step
|
|
277
|
+
*/
|
|
278
|
+
function transformStep(raw, existingIds, errors) {
|
|
279
|
+
const id = raw.id;
|
|
280
|
+
if (!id) {
|
|
281
|
+
errors.push({ code: "MISSING_STEP_ID", message: "Step must have an 'id' field" });
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
if (existingIds.has(id)) {
|
|
285
|
+
errors.push({ code: "DUPLICATE_STEP_ID", message: `Duplicate step id '${id}'` });
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
// Compute step
|
|
289
|
+
if ("compute" in raw) {
|
|
290
|
+
const assignments = [];
|
|
291
|
+
const compute = raw.compute;
|
|
292
|
+
for (const [variable, exprValue] of Object.entries(compute)) {
|
|
293
|
+
try {
|
|
294
|
+
let expression;
|
|
295
|
+
if (typeof exprValue === "string") {
|
|
296
|
+
expression = parseExpression(exprValue);
|
|
297
|
+
}
|
|
298
|
+
else if (typeof exprValue === "number") {
|
|
299
|
+
expression = {
|
|
300
|
+
kind: "literal",
|
|
301
|
+
value: exprValue,
|
|
302
|
+
type: Number.isInteger(exprValue) ? "int" : "float",
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
else if (typeof exprValue === "boolean") {
|
|
306
|
+
expression = { kind: "literal", value: exprValue, type: "bool" };
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
throw new Error(`Unsupported value type: ${typeof exprValue}`);
|
|
310
|
+
}
|
|
311
|
+
assignments.push({ variable, expression });
|
|
312
|
+
}
|
|
313
|
+
catch (e) {
|
|
314
|
+
errors.push({
|
|
315
|
+
code: "EXPRESSION_PARSE_ERROR",
|
|
316
|
+
message: `Failed to parse expression for '${variable}': ${e.message}`,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return {
|
|
321
|
+
kind: "compute",
|
|
322
|
+
id,
|
|
323
|
+
assignments,
|
|
324
|
+
dependsOn: [],
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
// Conditional step
|
|
328
|
+
if ("if" in raw && ("then" in raw || "action" in raw)) {
|
|
329
|
+
// Simple conditional with action
|
|
330
|
+
if ("action" in raw) {
|
|
331
|
+
const conditionStr = raw.if;
|
|
332
|
+
let condition;
|
|
333
|
+
try {
|
|
334
|
+
condition = parseExpression(conditionStr);
|
|
335
|
+
}
|
|
336
|
+
catch (e) {
|
|
337
|
+
errors.push({
|
|
338
|
+
code: "EXPRESSION_PARSE_ERROR",
|
|
339
|
+
message: `Failed to parse condition: ${e.message}`,
|
|
340
|
+
});
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
// Transform the action
|
|
344
|
+
const action = transformAction(raw.action, errors);
|
|
345
|
+
if (!action)
|
|
346
|
+
return null;
|
|
347
|
+
const constraints = transformConstraints(raw.constraints, errors);
|
|
348
|
+
const onFailure = raw.on_failure ?? "revert";
|
|
349
|
+
// Create action step with condition check
|
|
350
|
+
const actionStep = {
|
|
351
|
+
kind: "action",
|
|
352
|
+
id,
|
|
353
|
+
action,
|
|
354
|
+
constraints,
|
|
355
|
+
onFailure: onFailure,
|
|
356
|
+
dependsOn: [],
|
|
357
|
+
};
|
|
358
|
+
// Wrap in conditional
|
|
359
|
+
const _conditionalStep = {
|
|
360
|
+
kind: "conditional",
|
|
361
|
+
id: `${id}_cond`,
|
|
362
|
+
condition,
|
|
363
|
+
thenSteps: [id],
|
|
364
|
+
elseSteps: [],
|
|
365
|
+
dependsOn: [],
|
|
366
|
+
};
|
|
367
|
+
// Return the action step (conditional logic handled at execution)
|
|
368
|
+
// For now, store condition in dependsOn comment
|
|
369
|
+
return actionStep;
|
|
370
|
+
}
|
|
371
|
+
// Full conditional with then/else
|
|
372
|
+
const conditionStr = raw.if;
|
|
373
|
+
let condition;
|
|
374
|
+
try {
|
|
375
|
+
condition = parseExpression(conditionStr);
|
|
376
|
+
}
|
|
377
|
+
catch (e) {
|
|
378
|
+
errors.push({
|
|
379
|
+
code: "EXPRESSION_PARSE_ERROR",
|
|
380
|
+
message: `Failed to parse condition: ${e.message}`,
|
|
381
|
+
});
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
kind: "conditional",
|
|
386
|
+
id,
|
|
387
|
+
condition,
|
|
388
|
+
thenSteps: raw.then ?? [],
|
|
389
|
+
elseSteps: raw.else ?? [],
|
|
390
|
+
dependsOn: [],
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
// Action step (without condition)
|
|
394
|
+
if ("action" in raw) {
|
|
395
|
+
const action = transformAction(raw.action, errors);
|
|
396
|
+
if (!action)
|
|
397
|
+
return null;
|
|
398
|
+
const constraints = transformConstraints(raw.constraints, errors);
|
|
399
|
+
const onFailure = raw.on_failure ?? "revert";
|
|
400
|
+
return {
|
|
401
|
+
kind: "action",
|
|
402
|
+
id,
|
|
403
|
+
action,
|
|
404
|
+
skill: raw.skill,
|
|
405
|
+
constraints,
|
|
406
|
+
outputBinding: raw.output,
|
|
407
|
+
onFailure: onFailure,
|
|
408
|
+
dependsOn: [],
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
// Loop step
|
|
412
|
+
if ("repeat" in raw || "for" in raw || "loop" in raw) {
|
|
413
|
+
return transformLoopStep(raw, id, errors);
|
|
414
|
+
}
|
|
415
|
+
// Try step (from atomic blocks or explicit try)
|
|
416
|
+
if ("try" in raw) {
|
|
417
|
+
return transformTryStep(raw, id);
|
|
418
|
+
}
|
|
419
|
+
// Wait step
|
|
420
|
+
if ("wait" in raw) {
|
|
421
|
+
return {
|
|
422
|
+
kind: "wait",
|
|
423
|
+
id,
|
|
424
|
+
duration: raw.wait,
|
|
425
|
+
dependsOn: [],
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
// Emit step
|
|
429
|
+
if ("emit" in raw) {
|
|
430
|
+
const emit = raw.emit;
|
|
431
|
+
const event = emit.event;
|
|
432
|
+
const dataRaw = emit.data;
|
|
433
|
+
const data = {};
|
|
434
|
+
for (const [key, exprStr] of Object.entries(dataRaw)) {
|
|
435
|
+
try {
|
|
436
|
+
data[key] = parseExpression(exprStr);
|
|
437
|
+
}
|
|
438
|
+
catch (e) {
|
|
439
|
+
errors.push({
|
|
440
|
+
code: "EXPRESSION_PARSE_ERROR",
|
|
441
|
+
message: `Failed to parse emit data '${key}': ${e.message}`,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return {
|
|
446
|
+
kind: "emit",
|
|
447
|
+
id,
|
|
448
|
+
event,
|
|
449
|
+
data,
|
|
450
|
+
dependsOn: [],
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
// Halt step (if explicitly marked)
|
|
454
|
+
if ("halt" in raw) {
|
|
455
|
+
return {
|
|
456
|
+
kind: "halt",
|
|
457
|
+
id,
|
|
458
|
+
reason: raw.halt,
|
|
459
|
+
dependsOn: [],
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
// Advisory step (AI consultation)
|
|
463
|
+
if ("advisory" in raw) {
|
|
464
|
+
const advisory = raw.advisory;
|
|
465
|
+
const prompt = advisory.prompt;
|
|
466
|
+
const advisor = advisory.advisor ?? "default";
|
|
467
|
+
const timeout = advisory.timeout ?? 30;
|
|
468
|
+
const fallbackValue = advisory.fallback ?? true;
|
|
469
|
+
const outputBinding = advisory.output ?? `${id}_result`;
|
|
470
|
+
const outputSchemaRaw = advisory.output_schema;
|
|
471
|
+
const outputSchema = outputSchemaRaw
|
|
472
|
+
? parseOutputSchema(outputSchemaRaw)
|
|
473
|
+
: { type: "boolean" };
|
|
474
|
+
return {
|
|
475
|
+
kind: "advisory",
|
|
476
|
+
id,
|
|
477
|
+
advisor,
|
|
478
|
+
prompt,
|
|
479
|
+
outputSchema,
|
|
480
|
+
outputBinding,
|
|
481
|
+
timeout,
|
|
482
|
+
fallback: fallbackToExpression(fallbackValue, errors),
|
|
483
|
+
dependsOn: [],
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
// Parallel step
|
|
487
|
+
if ("parallel" in raw) {
|
|
488
|
+
const parallel = raw.parallel;
|
|
489
|
+
const branchesRaw = parallel.branches ?? [];
|
|
490
|
+
const branches = branchesRaw.map((b) => ({
|
|
491
|
+
id: `${id}_${b.name}`,
|
|
492
|
+
name: b.name,
|
|
493
|
+
steps: b.steps ?? [],
|
|
494
|
+
}));
|
|
495
|
+
const joinRaw = parallel.join;
|
|
496
|
+
let join = { type: "all" };
|
|
497
|
+
if (joinRaw?.type) {
|
|
498
|
+
const type = joinRaw.type;
|
|
499
|
+
if (type === "best") {
|
|
500
|
+
const metricStr = joinRaw.metric;
|
|
501
|
+
join = {
|
|
502
|
+
type: "best",
|
|
503
|
+
metric: metricStr ? parseExpression(metricStr) : parseExpression("0"),
|
|
504
|
+
order: joinRaw.order ?? "max",
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
else if (type === "any") {
|
|
508
|
+
join = { type: "any", count: joinRaw.count ?? 1 };
|
|
509
|
+
}
|
|
510
|
+
else if (type === "majority") {
|
|
511
|
+
join = { type: "majority" };
|
|
512
|
+
}
|
|
513
|
+
else if (type === "first") {
|
|
514
|
+
join = { type: "first" };
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
join = { type: "all" };
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return {
|
|
521
|
+
kind: "parallel",
|
|
522
|
+
id,
|
|
523
|
+
branches,
|
|
524
|
+
join,
|
|
525
|
+
onFail: parallel.on_fail ?? "abort",
|
|
526
|
+
timeout: parallel.timeout,
|
|
527
|
+
outputBinding: raw.output,
|
|
528
|
+
dependsOn: [],
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
// Pipeline step
|
|
532
|
+
if ("pipeline" in raw) {
|
|
533
|
+
const pipeline = raw.pipeline;
|
|
534
|
+
const sourceStr = pipeline.source;
|
|
535
|
+
const stagesRaw = pipeline.stages ?? [];
|
|
536
|
+
const stages = stagesRaw.map((stage) => {
|
|
537
|
+
const op = stage.op;
|
|
538
|
+
if (op === "reduce") {
|
|
539
|
+
return {
|
|
540
|
+
op: "reduce",
|
|
541
|
+
step: stage.step,
|
|
542
|
+
initial: parseExpression(String(stage.initial ?? "0")),
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
if (op === "take") {
|
|
546
|
+
return { op: "take", count: stage.count };
|
|
547
|
+
}
|
|
548
|
+
if (op === "skip") {
|
|
549
|
+
return { op: "skip", count: stage.count };
|
|
550
|
+
}
|
|
551
|
+
if (op === "sort") {
|
|
552
|
+
return {
|
|
553
|
+
op: "sort",
|
|
554
|
+
by: parseExpression(String(stage.by ?? "0")),
|
|
555
|
+
order: stage.order ?? "asc",
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
if (op === "where") {
|
|
559
|
+
return { op: "where", predicate: parseExpression(String(stage.predicate ?? "false")) };
|
|
560
|
+
}
|
|
561
|
+
return { op: op ?? "map", step: stage.step };
|
|
562
|
+
});
|
|
563
|
+
return {
|
|
564
|
+
kind: "pipeline",
|
|
565
|
+
id,
|
|
566
|
+
source: parseExpression(sourceStr),
|
|
567
|
+
stages,
|
|
568
|
+
parallel: pipeline.parallel ?? false,
|
|
569
|
+
outputBinding: raw.output,
|
|
570
|
+
dependsOn: [],
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
errors.push({ code: "UNKNOWN_STEP_TYPE", message: `Unknown step type for id '${id}'` });
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Transform action object
|
|
578
|
+
*/
|
|
579
|
+
function transformAction(raw, errors) {
|
|
580
|
+
const type = raw.type;
|
|
581
|
+
switch (type) {
|
|
582
|
+
case "swap":
|
|
583
|
+
return {
|
|
584
|
+
type: "swap",
|
|
585
|
+
venue: raw.venue,
|
|
586
|
+
assetIn: raw.asset_in,
|
|
587
|
+
assetOut: raw.asset_out,
|
|
588
|
+
amount: parseExpressionSafe(raw.amount, errors),
|
|
589
|
+
mode: raw.mode ?? "exact_in",
|
|
590
|
+
};
|
|
591
|
+
case "lend":
|
|
592
|
+
return {
|
|
593
|
+
type: "lend",
|
|
594
|
+
venue: raw.venue,
|
|
595
|
+
asset: raw.asset,
|
|
596
|
+
amount: raw.amount === "max" ? "max" : parseExpressionSafe(raw.amount, errors),
|
|
597
|
+
};
|
|
598
|
+
case "withdraw":
|
|
599
|
+
return {
|
|
600
|
+
type: "withdraw",
|
|
601
|
+
venue: raw.venue,
|
|
602
|
+
asset: raw.asset,
|
|
603
|
+
amount: raw.amount === "max" ? "max" : parseExpressionSafe(raw.amount, errors),
|
|
604
|
+
};
|
|
605
|
+
case "borrow":
|
|
606
|
+
return {
|
|
607
|
+
type: "borrow",
|
|
608
|
+
venue: raw.venue,
|
|
609
|
+
asset: raw.asset,
|
|
610
|
+
amount: parseExpressionSafe(raw.amount, errors),
|
|
611
|
+
collateral: raw.collateral,
|
|
612
|
+
};
|
|
613
|
+
case "repay":
|
|
614
|
+
return {
|
|
615
|
+
type: "repay",
|
|
616
|
+
venue: raw.venue,
|
|
617
|
+
asset: raw.asset,
|
|
618
|
+
amount: raw.amount === "max" ? "max" : parseExpressionSafe(raw.amount, errors),
|
|
619
|
+
};
|
|
620
|
+
case "stake":
|
|
621
|
+
return {
|
|
622
|
+
type: "stake",
|
|
623
|
+
venue: raw.venue,
|
|
624
|
+
asset: raw.asset,
|
|
625
|
+
amount: parseExpressionSafe(raw.amount, errors),
|
|
626
|
+
};
|
|
627
|
+
case "unstake":
|
|
628
|
+
return {
|
|
629
|
+
type: "unstake",
|
|
630
|
+
venue: raw.venue,
|
|
631
|
+
asset: raw.asset,
|
|
632
|
+
amount: raw.amount === "max" ? "max" : parseExpressionSafe(raw.amount, errors),
|
|
633
|
+
};
|
|
634
|
+
case "claim":
|
|
635
|
+
return {
|
|
636
|
+
type: "claim",
|
|
637
|
+
venue: raw.venue,
|
|
638
|
+
assets: raw.assets,
|
|
639
|
+
};
|
|
640
|
+
case "bridge": {
|
|
641
|
+
const toChainValue = raw.to_chain ?? raw.toChain;
|
|
642
|
+
if (toChainValue === undefined) {
|
|
643
|
+
errors.push({
|
|
644
|
+
code: "MISSING_BRIDGE_CHAIN",
|
|
645
|
+
message: "Bridge action requires to_chain",
|
|
646
|
+
});
|
|
647
|
+
return null;
|
|
648
|
+
}
|
|
649
|
+
return {
|
|
650
|
+
type: "bridge",
|
|
651
|
+
venue: raw.venue,
|
|
652
|
+
asset: raw.asset,
|
|
653
|
+
amount: parseExpressionSafe(raw.amount, errors),
|
|
654
|
+
toChain: parseExpressionSafe(toChainValue, errors),
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
case "transfer": {
|
|
658
|
+
const toValue = raw.to;
|
|
659
|
+
const isAddress = typeof toValue === "string" && toValue.startsWith("0x") && toValue.length === 42;
|
|
660
|
+
return {
|
|
661
|
+
type: "transfer",
|
|
662
|
+
asset: raw.asset,
|
|
663
|
+
amount: parseExpressionSafe(raw.amount, errors),
|
|
664
|
+
to: isAddress ? toValue : parseExpressionSafe(toValue, errors),
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
default:
|
|
668
|
+
errors.push({ code: "UNKNOWN_ACTION_TYPE", message: `Unknown action type '${type}'` });
|
|
669
|
+
return null;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Parse expression safely, returning a literal on failure
|
|
674
|
+
*/
|
|
675
|
+
function parseExpressionSafe(input, errors) {
|
|
676
|
+
if (typeof input === "number") {
|
|
677
|
+
return { kind: "literal", value: input, type: "int" };
|
|
678
|
+
}
|
|
679
|
+
if (typeof input === "boolean") {
|
|
680
|
+
return { kind: "literal", value: input, type: "bool" };
|
|
681
|
+
}
|
|
682
|
+
try {
|
|
683
|
+
return parseExpression(input);
|
|
684
|
+
}
|
|
685
|
+
catch (e) {
|
|
686
|
+
errors.push({
|
|
687
|
+
code: "EXPRESSION_PARSE_ERROR",
|
|
688
|
+
message: `Failed to parse expression '${input}': ${e.message}`,
|
|
689
|
+
});
|
|
690
|
+
return { kind: "literal", value: 0, type: "int" };
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
function fallbackToExpression(input, errors) {
|
|
694
|
+
if (input && typeof input === "object") {
|
|
695
|
+
if ("__expr" in input) {
|
|
696
|
+
const exprValue = input.__expr;
|
|
697
|
+
try {
|
|
698
|
+
return parseExpression(String(exprValue));
|
|
699
|
+
}
|
|
700
|
+
catch (e) {
|
|
701
|
+
errors.push({
|
|
702
|
+
code: "EXPRESSION_PARSE_ERROR",
|
|
703
|
+
message: `Failed to parse fallback expression '${exprValue}': ${e.message}`,
|
|
704
|
+
});
|
|
705
|
+
return { kind: "literal", value: false, type: "bool" };
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
if ("__literal" in input) {
|
|
709
|
+
return literalFromValue(input.__literal);
|
|
710
|
+
}
|
|
711
|
+
return literalFromValue(input);
|
|
712
|
+
}
|
|
713
|
+
if (typeof input === "boolean") {
|
|
714
|
+
return { kind: "literal", value: input, type: "bool" };
|
|
715
|
+
}
|
|
716
|
+
if (typeof input === "number") {
|
|
717
|
+
return {
|
|
718
|
+
kind: "literal",
|
|
719
|
+
value: input,
|
|
720
|
+
type: Number.isInteger(input) ? "int" : "float",
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
if (typeof input === "bigint") {
|
|
724
|
+
return { kind: "literal", value: input, type: "int" };
|
|
725
|
+
}
|
|
726
|
+
if (typeof input === "string") {
|
|
727
|
+
return { kind: "literal", value: input, type: "string" };
|
|
728
|
+
}
|
|
729
|
+
try {
|
|
730
|
+
return parseExpression(String(input));
|
|
731
|
+
}
|
|
732
|
+
catch (e) {
|
|
733
|
+
errors.push({
|
|
734
|
+
code: "EXPRESSION_PARSE_ERROR",
|
|
735
|
+
message: `Failed to parse fallback expression '${String(input)}': ${e.message}`,
|
|
736
|
+
});
|
|
737
|
+
return { kind: "literal", value: false, type: "bool" };
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
function literalFromValue(input) {
|
|
741
|
+
if (typeof input === "boolean") {
|
|
742
|
+
return { kind: "literal", value: input, type: "bool" };
|
|
743
|
+
}
|
|
744
|
+
if (typeof input === "number") {
|
|
745
|
+
return {
|
|
746
|
+
kind: "literal",
|
|
747
|
+
value: input,
|
|
748
|
+
type: Number.isInteger(input) ? "int" : "float",
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
if (typeof input === "bigint") {
|
|
752
|
+
return { kind: "literal", value: input, type: "int" };
|
|
753
|
+
}
|
|
754
|
+
if (typeof input === "string") {
|
|
755
|
+
return { kind: "literal", value: input, type: "string" };
|
|
756
|
+
}
|
|
757
|
+
return { kind: "literal", value: input, type: "json" };
|
|
758
|
+
}
|
|
759
|
+
function parseOutputSchema(raw) {
|
|
760
|
+
const type = raw.type;
|
|
761
|
+
switch (type) {
|
|
762
|
+
case "enum":
|
|
763
|
+
return {
|
|
764
|
+
type: "enum",
|
|
765
|
+
values: raw.values ?? [],
|
|
766
|
+
};
|
|
767
|
+
case "number":
|
|
768
|
+
return {
|
|
769
|
+
type: "number",
|
|
770
|
+
min: raw.min,
|
|
771
|
+
max: raw.max,
|
|
772
|
+
};
|
|
773
|
+
case "string":
|
|
774
|
+
return {
|
|
775
|
+
type: "string",
|
|
776
|
+
minLength: raw.min_length ?? raw.minLength,
|
|
777
|
+
maxLength: raw.max_length ?? raw.maxLength,
|
|
778
|
+
pattern: raw.pattern,
|
|
779
|
+
};
|
|
780
|
+
case "object": {
|
|
781
|
+
const fieldsRaw = raw.fields;
|
|
782
|
+
const fields = fieldsRaw
|
|
783
|
+
? Object.fromEntries(Object.entries(fieldsRaw).map(([key, value]) => [
|
|
784
|
+
key,
|
|
785
|
+
parseOutputSchema(value),
|
|
786
|
+
]))
|
|
787
|
+
: undefined;
|
|
788
|
+
return { type: "object", fields };
|
|
789
|
+
}
|
|
790
|
+
case "array": {
|
|
791
|
+
const itemsRaw = raw.items;
|
|
792
|
+
return {
|
|
793
|
+
type: "array",
|
|
794
|
+
items: itemsRaw ? parseOutputSchema(itemsRaw) : undefined,
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
default:
|
|
798
|
+
return { type: "boolean" };
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Transform constraints
|
|
803
|
+
*/
|
|
804
|
+
function transformConstraints(raw, errors) {
|
|
805
|
+
if (!raw)
|
|
806
|
+
return {};
|
|
807
|
+
const minOutputRaw = (raw.min_output ?? raw.minOutput);
|
|
808
|
+
const maxInputRaw = (raw.max_input ?? raw.maxInput);
|
|
809
|
+
const minLiquidityRaw = (raw.min_liquidity ?? raw.minLiquidity);
|
|
810
|
+
const maxGasRaw = (raw.max_gas ?? raw.maxGas);
|
|
811
|
+
const maxPriceImpactRaw = (raw.max_price_impact ?? raw.maxPriceImpact);
|
|
812
|
+
const requireQuoteRaw = (raw.require_quote ?? raw.requireQuote);
|
|
813
|
+
const requireSimulationRaw = (raw.require_simulation ?? raw.requireSimulation);
|
|
814
|
+
return {
|
|
815
|
+
maxSlippageBps: raw.max_slippage,
|
|
816
|
+
maxPriceImpactBps: typeof maxPriceImpactRaw === "number"
|
|
817
|
+
? maxPriceImpactRaw
|
|
818
|
+
: typeof maxPriceImpactRaw === "string"
|
|
819
|
+
? Number.parseFloat(maxPriceImpactRaw)
|
|
820
|
+
: undefined,
|
|
821
|
+
deadline: raw.deadline,
|
|
822
|
+
minOutput: minOutputRaw !== undefined ? parseExpressionSafe(minOutputRaw, errors) : undefined,
|
|
823
|
+
maxInput: maxInputRaw !== undefined ? parseExpressionSafe(maxInputRaw, errors) : undefined,
|
|
824
|
+
minLiquidity: minLiquidityRaw !== undefined ? parseExpressionSafe(minLiquidityRaw, errors) : undefined,
|
|
825
|
+
maxGas: maxGasRaw !== undefined ? parseExpressionSafe(maxGasRaw, errors) : undefined,
|
|
826
|
+
requireQuote: requireQuoteRaw !== undefined ? parseExpressionSafe(requireQuoteRaw, errors) : undefined,
|
|
827
|
+
requireSimulation: requireSimulationRaw !== undefined
|
|
828
|
+
? parseExpressionSafe(requireSimulationRaw, errors)
|
|
829
|
+
: undefined,
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Transform loop step
|
|
834
|
+
*/
|
|
835
|
+
function transformLoopStep(raw, id, errors) {
|
|
836
|
+
let loopType;
|
|
837
|
+
if ("repeat" in raw) {
|
|
838
|
+
loopType = { type: "repeat", count: raw.repeat };
|
|
839
|
+
}
|
|
840
|
+
else if ("for" in raw) {
|
|
841
|
+
const forClause = raw.for;
|
|
842
|
+
// Parse "var in expression"
|
|
843
|
+
const match = forClause.match(/^(\w+)\s+in\s+(.+)$/);
|
|
844
|
+
if (!match) {
|
|
845
|
+
errors.push({ code: "INVALID_FOR_CLAUSE", message: `Invalid for clause: ${forClause}` });
|
|
846
|
+
return null;
|
|
847
|
+
}
|
|
848
|
+
const [, variable, sourceStr] = match;
|
|
849
|
+
if (!variable || !sourceStr) {
|
|
850
|
+
errors.push({ code: "INVALID_FOR_CLAUSE", message: `Invalid for clause: ${forClause}` });
|
|
851
|
+
return null;
|
|
852
|
+
}
|
|
853
|
+
try {
|
|
854
|
+
const source = parseExpression(sourceStr);
|
|
855
|
+
loopType = { type: "for", variable, source };
|
|
856
|
+
}
|
|
857
|
+
catch (e) {
|
|
858
|
+
errors.push({
|
|
859
|
+
code: "EXPRESSION_PARSE_ERROR",
|
|
860
|
+
message: `Failed to parse for source: ${e.message}`,
|
|
861
|
+
});
|
|
862
|
+
return null;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
else if ("loop" in raw) {
|
|
866
|
+
const loop = raw.loop;
|
|
867
|
+
try {
|
|
868
|
+
const condition = parseExpression(loop.until);
|
|
869
|
+
loopType = { type: "until", condition };
|
|
870
|
+
}
|
|
871
|
+
catch (e) {
|
|
872
|
+
errors.push({
|
|
873
|
+
code: "EXPRESSION_PARSE_ERROR",
|
|
874
|
+
message: `Failed to parse loop condition: ${e.message}`,
|
|
875
|
+
});
|
|
876
|
+
return null;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
else {
|
|
880
|
+
return null;
|
|
881
|
+
}
|
|
882
|
+
const maxIterations = raw.max ?? raw.loop?.max ?? 100;
|
|
883
|
+
return {
|
|
884
|
+
kind: "loop",
|
|
885
|
+
id,
|
|
886
|
+
loopType,
|
|
887
|
+
bodySteps: raw.steps ?? [],
|
|
888
|
+
maxIterations,
|
|
889
|
+
parallel: raw.parallel,
|
|
890
|
+
outputBinding: raw.output,
|
|
891
|
+
dependsOn: [],
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Transform guard
|
|
896
|
+
*/
|
|
897
|
+
function transformGuard(raw, errors) {
|
|
898
|
+
if (!raw.id) {
|
|
899
|
+
errors.push({ code: "MISSING_GUARD_ID", message: "Guard must have an 'id' field" });
|
|
900
|
+
return null;
|
|
901
|
+
}
|
|
902
|
+
if (raw.advisory) {
|
|
903
|
+
// Advisory guard
|
|
904
|
+
return {
|
|
905
|
+
id: raw.id,
|
|
906
|
+
advisor: raw.advisory,
|
|
907
|
+
check: raw.check ?? "",
|
|
908
|
+
severity: raw.severity,
|
|
909
|
+
fallback: raw.fallback ?? true,
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
// Expression guard
|
|
913
|
+
if (!raw.check) {
|
|
914
|
+
errors.push({
|
|
915
|
+
code: "MISSING_GUARD_CHECK",
|
|
916
|
+
message: `Guard '${raw.id}' must have a 'check' field`,
|
|
917
|
+
});
|
|
918
|
+
return null;
|
|
919
|
+
}
|
|
920
|
+
try {
|
|
921
|
+
const check = parseExpression(raw.check);
|
|
922
|
+
return {
|
|
923
|
+
id: raw.id,
|
|
924
|
+
check,
|
|
925
|
+
severity: raw.severity,
|
|
926
|
+
message: raw.message ?? "",
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
catch (e) {
|
|
930
|
+
errors.push({
|
|
931
|
+
code: "EXPRESSION_PARSE_ERROR",
|
|
932
|
+
message: `Failed to parse guard check: ${e.message}`,
|
|
933
|
+
});
|
|
934
|
+
return null;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
const VALID_ERROR_TYPES = new Set([
|
|
938
|
+
"slippage_exceeded",
|
|
939
|
+
"insufficient_liquidity",
|
|
940
|
+
"insufficient_balance",
|
|
941
|
+
"venue_unavailable",
|
|
942
|
+
"deadline_exceeded",
|
|
943
|
+
"simulation_failed",
|
|
944
|
+
"policy_violation",
|
|
945
|
+
"guard_failed",
|
|
946
|
+
"tx_reverted",
|
|
947
|
+
"gas_exceeded",
|
|
948
|
+
]);
|
|
949
|
+
/**
|
|
950
|
+
* Transform a try step from SpellSource into TryStep IR.
|
|
951
|
+
*
|
|
952
|
+
* The transformer emits: { id, try: string[], catch: Array<{ error, action?, steps?, retry? }> }
|
|
953
|
+
* We convert to the TryStep type expected by the runtime.
|
|
954
|
+
*/
|
|
955
|
+
function transformTryStep(raw, id) {
|
|
956
|
+
const trySteps = raw.try ?? [];
|
|
957
|
+
const rawCatch = raw.catch ?? [];
|
|
958
|
+
const finallySteps = raw.finally ?? undefined;
|
|
959
|
+
const catchBlocks = rawCatch.map((c) => {
|
|
960
|
+
const errorField = c.error ?? "*";
|
|
961
|
+
const errorType = errorField === "*"
|
|
962
|
+
? "*"
|
|
963
|
+
: VALID_ERROR_TYPES.has(errorField)
|
|
964
|
+
? errorField
|
|
965
|
+
: "*";
|
|
966
|
+
// Map "revert" → "rollback" to match CatchBlock action type
|
|
967
|
+
let action;
|
|
968
|
+
if (c.action) {
|
|
969
|
+
const rawAction = c.action;
|
|
970
|
+
action = rawAction === "revert" ? "rollback" : rawAction;
|
|
971
|
+
}
|
|
972
|
+
const block = { errorType };
|
|
973
|
+
if (action)
|
|
974
|
+
block.action = action;
|
|
975
|
+
if (c.steps)
|
|
976
|
+
block.steps = c.steps;
|
|
977
|
+
if (c.retry) {
|
|
978
|
+
const retry = c.retry;
|
|
979
|
+
block.retry = {
|
|
980
|
+
maxAttempts: retry.maxAttempts ?? 3,
|
|
981
|
+
backoff: retry.backoff?.backoff ?? "none",
|
|
982
|
+
backoffBase: retry.backoffBase,
|
|
983
|
+
maxBackoff: retry.maxBackoff,
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
return block;
|
|
987
|
+
});
|
|
988
|
+
return {
|
|
989
|
+
kind: "try",
|
|
990
|
+
id,
|
|
991
|
+
trySteps,
|
|
992
|
+
catchBlocks,
|
|
993
|
+
finallySteps: finallySteps && finallySteps.length > 0 ? finallySteps : undefined,
|
|
994
|
+
dependsOn: [],
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
//# sourceMappingURL=ir-generator.js.map
|