@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.
@@ -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() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sweny-ai/core",
3
- "version": "0.1.59",
3
+ "version": "0.1.60",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "sweny": "./dist/cli/main.js"