@sweny-ai/core 0.1.59 → 0.1.60
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/__tests__/studio.test.js +158 -0
- package/package.json +1 -1
|
@@ -139,6 +139,157 @@ describe("workflowToFlow", () => {
|
|
|
139
139
|
});
|
|
140
140
|
});
|
|
141
141
|
});
|
|
142
|
+
describe("edge IDs and edgeIndex", () => {
|
|
143
|
+
it("generates unique edge IDs using array index", () => {
|
|
144
|
+
const { edges } = workflowToFlow(testWorkflow);
|
|
145
|
+
const ids = edges.map((e) => e.id);
|
|
146
|
+
expect(ids[0]).toBe("edge-0");
|
|
147
|
+
expect(ids[1]).toBe("edge-1");
|
|
148
|
+
// All IDs are unique
|
|
149
|
+
expect(new Set(ids).size).toBe(ids.length);
|
|
150
|
+
});
|
|
151
|
+
it("assigns correct edgeIndex matching array position", () => {
|
|
152
|
+
const { edges } = workflowToFlow(testWorkflow);
|
|
153
|
+
edges.forEach((edge, i) => {
|
|
154
|
+
expect(edge.edgeIndex).toBe(i);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
it("produces unique IDs for multiple edges between the same node pair", () => {
|
|
158
|
+
const wf = {
|
|
159
|
+
id: "multi-edge",
|
|
160
|
+
name: "Multi-Edge",
|
|
161
|
+
description: "",
|
|
162
|
+
entry: "a",
|
|
163
|
+
nodes: {
|
|
164
|
+
a: { name: "A", instruction: "A", skills: [] },
|
|
165
|
+
b: { name: "B", instruction: "B", skills: [] },
|
|
166
|
+
},
|
|
167
|
+
edges: [
|
|
168
|
+
{ from: "a", to: "b" },
|
|
169
|
+
{ from: "a", to: "b", when: "error detected" },
|
|
170
|
+
{ from: "a", to: "b", when: "retry needed" },
|
|
171
|
+
],
|
|
172
|
+
};
|
|
173
|
+
const { edges } = workflowToFlow(wf);
|
|
174
|
+
expect(edges).toHaveLength(3);
|
|
175
|
+
const ids = edges.map((e) => e.id);
|
|
176
|
+
expect(new Set(ids).size).toBe(3);
|
|
177
|
+
expect(ids).toEqual(["edge-0", "edge-1", "edge-2"]);
|
|
178
|
+
});
|
|
179
|
+
it("produces stable IDs across multiple conversions", () => {
|
|
180
|
+
const first = workflowToFlow(testWorkflow).edges.map((e) => e.id);
|
|
181
|
+
const second = workflowToFlow(testWorkflow).edges.map((e) => e.id);
|
|
182
|
+
expect(first).toEqual(second);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
describe("max_iterations in conversion", () => {
|
|
186
|
+
const wfWithMaxIter = {
|
|
187
|
+
id: "retry-loop",
|
|
188
|
+
name: "Retry Loop",
|
|
189
|
+
description: "",
|
|
190
|
+
entry: "check",
|
|
191
|
+
nodes: {
|
|
192
|
+
check: { name: "Check", instruction: "Check it", skills: [] },
|
|
193
|
+
fix: { name: "Fix", instruction: "Fix it", skills: [] },
|
|
194
|
+
},
|
|
195
|
+
edges: [
|
|
196
|
+
{ from: "check", to: "fix", when: "issue found" },
|
|
197
|
+
{ from: "fix", to: "check", when: "needs re-check", max_iterations: 3 },
|
|
198
|
+
],
|
|
199
|
+
};
|
|
200
|
+
it("includes max_iterations in flow edge data", () => {
|
|
201
|
+
const { edges } = workflowToFlow(wfWithMaxIter);
|
|
202
|
+
const retryEdge = edges.find((e) => e.source === "fix" && e.target === "check");
|
|
203
|
+
expect(retryEdge?.data.max_iterations).toBe(3);
|
|
204
|
+
});
|
|
205
|
+
it("omits max_iterations when not set", () => {
|
|
206
|
+
const { edges } = workflowToFlow(wfWithMaxIter);
|
|
207
|
+
const firstEdge = edges.find((e) => e.source === "check" && e.target === "fix");
|
|
208
|
+
expect(firstEdge?.data.max_iterations).toBeUndefined();
|
|
209
|
+
});
|
|
210
|
+
it("round-trips max_iterations through flowToWorkflow", () => {
|
|
211
|
+
const { nodes, edges } = workflowToFlow(wfWithMaxIter);
|
|
212
|
+
const rebuilt = flowToWorkflow({
|
|
213
|
+
id: wfWithMaxIter.id,
|
|
214
|
+
name: wfWithMaxIter.name,
|
|
215
|
+
description: wfWithMaxIter.description,
|
|
216
|
+
entry: wfWithMaxIter.entry,
|
|
217
|
+
}, nodes, edges);
|
|
218
|
+
const retryEdge = rebuilt.edges.find((e) => e.from === "fix" && e.to === "check");
|
|
219
|
+
expect(retryEdge?.max_iterations).toBe(3);
|
|
220
|
+
// Non-retry edge should not have max_iterations
|
|
221
|
+
const normalEdge = rebuilt.edges.find((e) => e.from === "check" && e.to === "fix");
|
|
222
|
+
expect(normalEdge).not.toHaveProperty("max_iterations");
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
describe("Source type preservation", () => {
|
|
226
|
+
it("preserves inline string instructions through round-trip", () => {
|
|
227
|
+
const { nodes, edges } = workflowToFlow(testWorkflow);
|
|
228
|
+
const rebuilt = flowToWorkflow({ id: "x", name: "X", description: "", entry: "a" }, nodes, edges);
|
|
229
|
+
expect(rebuilt.nodes.a.instruction).toBe("Do A");
|
|
230
|
+
});
|
|
231
|
+
it("preserves {file:} Source through round-trip", () => {
|
|
232
|
+
const wf = {
|
|
233
|
+
id: "file-src",
|
|
234
|
+
name: "File Source",
|
|
235
|
+
description: "",
|
|
236
|
+
entry: "a",
|
|
237
|
+
nodes: {
|
|
238
|
+
a: { name: "A", instruction: { file: "./instructions/step-a.md" }, skills: [] },
|
|
239
|
+
},
|
|
240
|
+
edges: [],
|
|
241
|
+
};
|
|
242
|
+
const { nodes, edges } = workflowToFlow(wf);
|
|
243
|
+
const rebuilt = flowToWorkflow({ id: wf.id, name: wf.name, description: "", entry: "a" }, nodes, edges);
|
|
244
|
+
expect(rebuilt.nodes.a.instruction).toEqual({ file: "./instructions/step-a.md" });
|
|
245
|
+
});
|
|
246
|
+
it("preserves {url:} Source through round-trip", () => {
|
|
247
|
+
const wf = {
|
|
248
|
+
id: "url-src",
|
|
249
|
+
name: "URL Source",
|
|
250
|
+
description: "",
|
|
251
|
+
entry: "a",
|
|
252
|
+
nodes: {
|
|
253
|
+
a: { name: "A", instruction: { url: "https://example.com/instruction.md" }, skills: [] },
|
|
254
|
+
},
|
|
255
|
+
edges: [],
|
|
256
|
+
};
|
|
257
|
+
const { nodes, edges } = workflowToFlow(wf);
|
|
258
|
+
const rebuilt = flowToWorkflow({ id: wf.id, name: wf.name, description: "", entry: "a" }, nodes, edges);
|
|
259
|
+
expect(rebuilt.nodes.a.instruction).toEqual({ url: "https://example.com/instruction.md" });
|
|
260
|
+
});
|
|
261
|
+
it("preserves {inline:} Source through round-trip", () => {
|
|
262
|
+
const wf = {
|
|
263
|
+
id: "inline-src",
|
|
264
|
+
name: "Inline Source",
|
|
265
|
+
description: "",
|
|
266
|
+
entry: "a",
|
|
267
|
+
nodes: {
|
|
268
|
+
a: { name: "A", instruction: { inline: "Explicit inline text" }, skills: [] },
|
|
269
|
+
},
|
|
270
|
+
edges: [],
|
|
271
|
+
};
|
|
272
|
+
const { nodes, edges } = workflowToFlow(wf);
|
|
273
|
+
const rebuilt = flowToWorkflow({ id: wf.id, name: wf.name, description: "", entry: "a" }, nodes, edges);
|
|
274
|
+
expect(rebuilt.nodes.a.instruction).toEqual({ inline: "Explicit inline text" });
|
|
275
|
+
});
|
|
276
|
+
it("preserves {url:, type:} Source through round-trip", () => {
|
|
277
|
+
const wf = {
|
|
278
|
+
id: "url-type-src",
|
|
279
|
+
name: "URL Type Source",
|
|
280
|
+
description: "",
|
|
281
|
+
entry: "a",
|
|
282
|
+
nodes: {
|
|
283
|
+
a: { name: "A", instruction: { url: "https://example.com/data", type: "fetch" }, skills: [] },
|
|
284
|
+
},
|
|
285
|
+
edges: [],
|
|
286
|
+
};
|
|
287
|
+
const { nodes, edges } = workflowToFlow(wf);
|
|
288
|
+
const rebuilt = flowToWorkflow({ id: wf.id, name: wf.name, description: "", entry: "a" }, nodes, edges);
|
|
289
|
+
const src = rebuilt.nodes.a.instruction;
|
|
290
|
+
expect(src).toEqual({ url: "https://example.com/data", type: "fetch" });
|
|
291
|
+
});
|
|
292
|
+
});
|
|
142
293
|
describe("flowToWorkflow", () => {
|
|
143
294
|
it("round-trips correctly", () => {
|
|
144
295
|
const { nodes, edges } = workflowToFlow(testWorkflow);
|
|
@@ -160,6 +311,13 @@ describe("flowToWorkflow", () => {
|
|
|
160
311
|
const unconditional = rebuilt.edges.find((e) => e.from === "a" && e.to === "b");
|
|
161
312
|
expect(unconditional).not.toHaveProperty("when");
|
|
162
313
|
});
|
|
314
|
+
it("omits max_iterations when not set", () => {
|
|
315
|
+
const { nodes, edges } = workflowToFlow(testWorkflow);
|
|
316
|
+
const rebuilt = flowToWorkflow({ id: "x", name: "X", description: "", entry: "a" }, nodes, edges);
|
|
317
|
+
for (const edge of rebuilt.edges) {
|
|
318
|
+
expect(edge).not.toHaveProperty("max_iterations");
|
|
319
|
+
}
|
|
320
|
+
});
|
|
163
321
|
});
|
|
164
322
|
describe("applyExecutionEvent", () => {
|
|
165
323
|
function makeNodeDataMap() {
|