@funkai/agents 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/.generated/req.txt +1 -0
- package/.turbo/turbo-build.log +21 -0
- package/.turbo/turbo-test$colon$coverage.log +109 -0
- package/.turbo/turbo-test.log +141 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/CHANGELOG.md +16 -0
- package/ISSUES.md +540 -0
- package/LICENSE +21 -0
- package/README.md +128 -0
- package/banner.svg +97 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/core/agents/base/agent.ts.html +1705 -0
- package/coverage/lcov-report/core/agents/base/index.html +146 -0
- package/coverage/lcov-report/core/agents/base/output.ts.html +256 -0
- package/coverage/lcov-report/core/agents/base/utils.ts.html +694 -0
- package/coverage/lcov-report/core/agents/flow/engine.ts.html +928 -0
- package/coverage/lcov-report/core/agents/flow/flow-agent.ts.html +1462 -0
- package/coverage/lcov-report/core/agents/flow/index.html +146 -0
- package/coverage/lcov-report/core/agents/flow/messages.ts.html +508 -0
- package/coverage/lcov-report/core/agents/flow/steps/factory.ts.html +1975 -0
- package/coverage/lcov-report/core/agents/flow/steps/index.html +116 -0
- package/coverage/lcov-report/core/index.html +131 -0
- package/coverage/lcov-report/core/logger.ts.html +541 -0
- package/coverage/lcov-report/core/models/providers/index.html +116 -0
- package/coverage/lcov-report/core/models/providers/openai.ts.html +337 -0
- package/coverage/lcov-report/core/provider/index.html +131 -0
- package/coverage/lcov-report/core/provider/provider.ts.html +346 -0
- package/coverage/lcov-report/core/provider/usage.ts.html +376 -0
- package/coverage/lcov-report/core/tool.ts.html +577 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +221 -0
- package/coverage/lcov-report/lib/hooks.ts.html +262 -0
- package/coverage/lcov-report/lib/index.html +161 -0
- package/coverage/lcov-report/lib/middleware.ts.html +274 -0
- package/coverage/lcov-report/lib/runnable.ts.html +151 -0
- package/coverage/lcov-report/lib/trace.ts.html +520 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov-report/utils/attempt.ts.html +199 -0
- package/coverage/lcov-report/utils/error.ts.html +421 -0
- package/coverage/lcov-report/utils/index.html +176 -0
- package/coverage/lcov-report/utils/resolve.ts.html +208 -0
- package/coverage/lcov-report/utils/result.ts.html +538 -0
- package/coverage/lcov-report/utils/zod.ts.html +178 -0
- package/coverage/lcov.info +1566 -0
- package/dist/index.d.mts +2883 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2312 -0
- package/dist/index.mjs.map +1 -0
- package/docs/core/agent.md +231 -0
- package/docs/core/hooks.md +95 -0
- package/docs/core/overview.md +87 -0
- package/docs/core/step.md +279 -0
- package/docs/core/tools.md +98 -0
- package/docs/core/workflow.md +235 -0
- package/docs/guides/create-agent.md +224 -0
- package/docs/guides/create-tool.md +137 -0
- package/docs/guides/create-workflow.md +374 -0
- package/docs/overview.md +244 -0
- package/docs/provider/models.md +55 -0
- package/docs/provider/overview.md +106 -0
- package/docs/provider/usage.md +100 -0
- package/docs/research/experimental-context.md +167 -0
- package/docs/research/gap-analysis.md +86 -0
- package/docs/research/prepare-step-and-active-tools.md +138 -0
- package/docs/research/sub-agent-model.md +249 -0
- package/docs/troubleshooting.md +60 -0
- package/logo.svg +17 -0
- package/models.config.json +18 -0
- package/package.json +60 -0
- package/scripts/generate-models.ts +324 -0
- package/src/core/agents/base/agent.test.ts +1522 -0
- package/src/core/agents/base/agent.ts +547 -0
- package/src/core/agents/base/output.test.ts +93 -0
- package/src/core/agents/base/output.ts +57 -0
- package/src/core/agents/base/types.test-d.ts +69 -0
- package/src/core/agents/base/types.ts +503 -0
- package/src/core/agents/base/utils.test.ts +397 -0
- package/src/core/agents/base/utils.ts +197 -0
- package/src/core/agents/flow/engine.test.ts +452 -0
- package/src/core/agents/flow/engine.ts +281 -0
- package/src/core/agents/flow/flow-agent.test.ts +1027 -0
- package/src/core/agents/flow/flow-agent.ts +473 -0
- package/src/core/agents/flow/messages.test.ts +198 -0
- package/src/core/agents/flow/messages.ts +141 -0
- package/src/core/agents/flow/steps/agent.test.ts +280 -0
- package/src/core/agents/flow/steps/agent.ts +87 -0
- package/src/core/agents/flow/steps/all.test.ts +300 -0
- package/src/core/agents/flow/steps/all.ts +73 -0
- package/src/core/agents/flow/steps/builder.ts +124 -0
- package/src/core/agents/flow/steps/each.test.ts +257 -0
- package/src/core/agents/flow/steps/each.ts +61 -0
- package/src/core/agents/flow/steps/factory.test-d.ts +50 -0
- package/src/core/agents/flow/steps/factory.test.ts +1025 -0
- package/src/core/agents/flow/steps/factory.ts +645 -0
- package/src/core/agents/flow/steps/map.test.ts +273 -0
- package/src/core/agents/flow/steps/map.ts +75 -0
- package/src/core/agents/flow/steps/race.test.ts +290 -0
- package/src/core/agents/flow/steps/race.ts +59 -0
- package/src/core/agents/flow/steps/reduce.test.ts +310 -0
- package/src/core/agents/flow/steps/reduce.ts +73 -0
- package/src/core/agents/flow/steps/result.ts +27 -0
- package/src/core/agents/flow/steps/step.test.ts +402 -0
- package/src/core/agents/flow/steps/step.ts +51 -0
- package/src/core/agents/flow/steps/while.test.ts +283 -0
- package/src/core/agents/flow/steps/while.ts +75 -0
- package/src/core/agents/flow/types.ts +348 -0
- package/src/core/logger.test.ts +163 -0
- package/src/core/logger.ts +152 -0
- package/src/core/models/index.test.ts +137 -0
- package/src/core/models/index.ts +152 -0
- package/src/core/models/providers/openai.ts +84 -0
- package/src/core/provider/provider.test.ts +128 -0
- package/src/core/provider/provider.ts +99 -0
- package/src/core/provider/types.ts +98 -0
- package/src/core/provider/usage.test.ts +304 -0
- package/src/core/provider/usage.ts +97 -0
- package/src/core/tool.test.ts +65 -0
- package/src/core/tool.ts +164 -0
- package/src/core/types.ts +66 -0
- package/src/index.ts +95 -0
- package/src/lib/context.test.ts +86 -0
- package/src/lib/context.ts +49 -0
- package/src/lib/hooks.test.ts +102 -0
- package/src/lib/hooks.ts +59 -0
- package/src/lib/middleware.test.ts +122 -0
- package/src/lib/middleware.ts +63 -0
- package/src/lib/runnable.test.ts +41 -0
- package/src/lib/runnable.ts +22 -0
- package/src/lib/trace.test.ts +291 -0
- package/src/lib/trace.ts +145 -0
- package/src/models/index.ts +123 -0
- package/src/models/providers/index.ts +15 -0
- package/src/models/providers/openai.ts +84 -0
- package/src/testing/context.ts +32 -0
- package/src/testing/index.ts +2 -0
- package/src/testing/logger.ts +19 -0
- package/src/utils/attempt.test.ts +127 -0
- package/src/utils/attempt.ts +38 -0
- package/src/utils/error.test.ts +179 -0
- package/src/utils/error.ts +112 -0
- package/src/utils/resolve.test.ts +38 -0
- package/src/utils/resolve.ts +41 -0
- package/src/utils/result.test.ts +79 -0
- package/src/utils/result.ts +151 -0
- package/src/utils/zod.test.ts +69 -0
- package/src/utils/zod.ts +31 -0
- package/tsconfig.json +25 -0
- package/tsdown.config.ts +15 -0
- package/vitest.config.ts +46 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { createStepBuilder } from "@/core/agents/flow/steps/factory.js";
|
|
4
|
+
import { createMockCtx } from "@/testing/index.js";
|
|
5
|
+
|
|
6
|
+
describe("step()", () => {
|
|
7
|
+
it("returns ok: true with value on success", async () => {
|
|
8
|
+
const ctx = createMockCtx();
|
|
9
|
+
const $ = createStepBuilder({ ctx });
|
|
10
|
+
|
|
11
|
+
const result = await $.step({
|
|
12
|
+
id: "greet",
|
|
13
|
+
execute: async () => ({ greeting: "hello" }),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
expect(result.ok).toBe(true);
|
|
17
|
+
if (!result.ok) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
expect(result.value).toEqual({ greeting: "hello" });
|
|
21
|
+
expect(result.step.id).toBe("greet");
|
|
22
|
+
expect(result.step.type).toBe("step");
|
|
23
|
+
expect(result.step.index).toBe(0);
|
|
24
|
+
expect(result.duration).toBeGreaterThanOrEqual(0);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("returns ok: false with StepError on thrown error", async () => {
|
|
28
|
+
const ctx = createMockCtx();
|
|
29
|
+
const $ = createStepBuilder({ ctx });
|
|
30
|
+
|
|
31
|
+
const result = await $.step({
|
|
32
|
+
id: "fail",
|
|
33
|
+
execute: async () => {
|
|
34
|
+
throw new Error("boom");
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
expect(result.ok).toBe(false);
|
|
39
|
+
if (result.ok) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
expect(result.error.code).toBe("STEP_ERROR");
|
|
43
|
+
expect(result.error.message).toBe("boom");
|
|
44
|
+
expect(result.error.stepId).toBe("fail");
|
|
45
|
+
expect(result.error.cause).toBeInstanceOf(Error);
|
|
46
|
+
expect(result.step.id).toBe("fail");
|
|
47
|
+
expect(result.duration).toBeGreaterThanOrEqual(0);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("wraps non-Error thrown values into Error", async () => {
|
|
51
|
+
const ctx = createMockCtx();
|
|
52
|
+
const $ = createStepBuilder({ ctx });
|
|
53
|
+
|
|
54
|
+
const result = await $.step({
|
|
55
|
+
id: "string-throw",
|
|
56
|
+
execute: async () => {
|
|
57
|
+
// eslint-disable-next-line no-throw-literal
|
|
58
|
+
throw "raw string error";
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
expect(result.ok).toBe(false);
|
|
63
|
+
if (result.ok) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
expect(result.error.message).toBe("raw string error");
|
|
67
|
+
expect(result.error.cause).toBeInstanceOf(Error);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("serializes thrown plain objects as JSON instead of [object Object]", async () => {
|
|
71
|
+
const ctx = createMockCtx();
|
|
72
|
+
const $ = createStepBuilder({ ctx });
|
|
73
|
+
|
|
74
|
+
const result = await $.step({
|
|
75
|
+
id: "object-throw",
|
|
76
|
+
execute: async () => {
|
|
77
|
+
// eslint-disable-next-line no-throw-literal
|
|
78
|
+
throw { status: 400, message: "sandbox name too long" };
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
expect(result.ok).toBe(false);
|
|
83
|
+
if (result.ok) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
expect(result.error.message).toBe('{"status":400,"message":"sandbox name too long"}');
|
|
87
|
+
expect(result.error.cause).toBeInstanceOf(Error);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("fires hooks in order: onStart -> execute -> onFinish", async () => {
|
|
91
|
+
const order: string[] = [];
|
|
92
|
+
const ctx = createMockCtx();
|
|
93
|
+
const $ = createStepBuilder({ ctx });
|
|
94
|
+
|
|
95
|
+
await $.step({
|
|
96
|
+
id: "ordered",
|
|
97
|
+
onStart: () => {
|
|
98
|
+
order.push("onStart");
|
|
99
|
+
},
|
|
100
|
+
execute: async () => {
|
|
101
|
+
order.push("execute");
|
|
102
|
+
return { v: 1 };
|
|
103
|
+
},
|
|
104
|
+
onFinish: () => {
|
|
105
|
+
order.push("onFinish");
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
expect(order).toEqual(["onStart", "execute", "onFinish"]);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("fires onError instead of onFinish on failure", async () => {
|
|
113
|
+
const order: string[] = [];
|
|
114
|
+
const ctx = createMockCtx();
|
|
115
|
+
const $ = createStepBuilder({ ctx });
|
|
116
|
+
|
|
117
|
+
await $.step({
|
|
118
|
+
id: "err-hooks",
|
|
119
|
+
onStart: () => {
|
|
120
|
+
order.push("onStart");
|
|
121
|
+
},
|
|
122
|
+
execute: async () => {
|
|
123
|
+
order.push("execute");
|
|
124
|
+
throw new Error("fail");
|
|
125
|
+
},
|
|
126
|
+
onFinish: () => {
|
|
127
|
+
order.push("onFinish");
|
|
128
|
+
},
|
|
129
|
+
onError: () => {
|
|
130
|
+
order.push("onError");
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
expect(order).toEqual(["onStart", "execute", "onError"]);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("fires parent hooks after step hooks", async () => {
|
|
138
|
+
const order: string[] = [];
|
|
139
|
+
const ctx = createMockCtx();
|
|
140
|
+
const $ = createStepBuilder({
|
|
141
|
+
ctx,
|
|
142
|
+
parentHooks: {
|
|
143
|
+
onStepStart: () => {
|
|
144
|
+
order.push("parentStart");
|
|
145
|
+
},
|
|
146
|
+
onStepFinish: () => {
|
|
147
|
+
order.push("parentFinish");
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
await $.step({
|
|
153
|
+
id: "parent-hooks",
|
|
154
|
+
onStart: () => {
|
|
155
|
+
order.push("onStart");
|
|
156
|
+
},
|
|
157
|
+
execute: async () => {
|
|
158
|
+
order.push("execute");
|
|
159
|
+
return { v: 1 };
|
|
160
|
+
},
|
|
161
|
+
onFinish: () => {
|
|
162
|
+
order.push("onFinish");
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
expect(order).toEqual(["onStart", "parentStart", "execute", "onFinish", "parentFinish"]);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("parentHooks.onStepFinish fires on error with result undefined", async () => {
|
|
170
|
+
const parentFinish = vi.fn();
|
|
171
|
+
const ctx = createMockCtx();
|
|
172
|
+
const $ = createStepBuilder({
|
|
173
|
+
ctx,
|
|
174
|
+
parentHooks: { onStepFinish: parentFinish },
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
await $.step({
|
|
178
|
+
id: "parent-finish-on-error",
|
|
179
|
+
execute: async () => {
|
|
180
|
+
throw new Error("fail");
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
expect(parentFinish).toHaveBeenCalledTimes(1);
|
|
185
|
+
expect(parentFinish).toHaveBeenCalledWith(
|
|
186
|
+
expect.objectContaining({
|
|
187
|
+
step: expect.objectContaining({ id: "parent-finish-on-error" }),
|
|
188
|
+
result: undefined,
|
|
189
|
+
}),
|
|
190
|
+
);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("swallows hook errors without breaking execution", async () => {
|
|
194
|
+
const ctx = createMockCtx();
|
|
195
|
+
const $ = createStepBuilder({ ctx });
|
|
196
|
+
|
|
197
|
+
const result = await $.step({
|
|
198
|
+
id: "safe",
|
|
199
|
+
onStart: () => {
|
|
200
|
+
throw new Error("hook boom");
|
|
201
|
+
},
|
|
202
|
+
execute: async () => ({ value: 42 }),
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
expect(result.ok).toBe(true);
|
|
206
|
+
if (!result.ok) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
expect(result.value).toEqual({ value: 42 });
|
|
210
|
+
expect(ctx.log.warn).toHaveBeenCalledWith("hook error", { error: "hook boom" });
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("registers trace entries on success", async () => {
|
|
214
|
+
const ctx = createMockCtx();
|
|
215
|
+
const $ = createStepBuilder({ ctx });
|
|
216
|
+
|
|
217
|
+
await $.step({
|
|
218
|
+
id: "traced",
|
|
219
|
+
execute: async () => ({ v: 1 }),
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
expect(ctx.trace).toHaveLength(1);
|
|
223
|
+
const traceEntry = ctx.trace[0];
|
|
224
|
+
if (traceEntry === undefined) {
|
|
225
|
+
throw new Error("Expected trace entry");
|
|
226
|
+
}
|
|
227
|
+
expect(traceEntry.id).toBe("traced");
|
|
228
|
+
expect(traceEntry.type).toBe("step");
|
|
229
|
+
expect(traceEntry.output).toEqual({ v: 1 });
|
|
230
|
+
expect(traceEntry.finishedAt).toBeGreaterThan(0);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("records trace error on failure", async () => {
|
|
234
|
+
const ctx = createMockCtx();
|
|
235
|
+
const $ = createStepBuilder({ ctx });
|
|
236
|
+
|
|
237
|
+
await $.step({
|
|
238
|
+
id: "trace-err",
|
|
239
|
+
execute: async () => {
|
|
240
|
+
throw new Error("trace-boom");
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const traceEntry = ctx.trace[0];
|
|
245
|
+
if (traceEntry === undefined) {
|
|
246
|
+
throw new Error("Expected trace entry");
|
|
247
|
+
}
|
|
248
|
+
expect(traceEntry.error).toBeInstanceOf(Error);
|
|
249
|
+
if (traceEntry.error === undefined) {
|
|
250
|
+
throw new Error("Expected trace error");
|
|
251
|
+
}
|
|
252
|
+
expect(traceEntry.error.message).toBe("trace-boom");
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("increments step index across calls", async () => {
|
|
256
|
+
const ctx = createMockCtx();
|
|
257
|
+
const $ = createStepBuilder({ ctx });
|
|
258
|
+
|
|
259
|
+
const r1 = await $.step({ id: "a", execute: async () => ({}) });
|
|
260
|
+
const r2 = await $.step({ id: "b", execute: async () => ({}) });
|
|
261
|
+
const r3 = await $.step({ id: "c", execute: async () => ({}) });
|
|
262
|
+
|
|
263
|
+
expect(r1.step.index).toBe(0);
|
|
264
|
+
expect(r2.step.index).toBe(1);
|
|
265
|
+
expect(r3.step.index).toBe(2);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("provides child $ for nested operations", async () => {
|
|
269
|
+
const ctx = createMockCtx();
|
|
270
|
+
const $$ = createStepBuilder({ ctx });
|
|
271
|
+
|
|
272
|
+
await $$.step({
|
|
273
|
+
id: "parent",
|
|
274
|
+
execute: async ({ $ }) => {
|
|
275
|
+
await $.step({ id: "child", execute: async () => ({}) });
|
|
276
|
+
return {};
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
expect(ctx.trace).toHaveLength(1);
|
|
281
|
+
const traceEntry = ctx.trace[0];
|
|
282
|
+
if (traceEntry === undefined) {
|
|
283
|
+
throw new Error("Expected trace entry");
|
|
284
|
+
}
|
|
285
|
+
expect(traceEntry.children).toHaveLength(1);
|
|
286
|
+
if (traceEntry.children === undefined) {
|
|
287
|
+
throw new Error("Expected trace children");
|
|
288
|
+
}
|
|
289
|
+
const childEntry = traceEntry.children[0];
|
|
290
|
+
if (childEntry === undefined) {
|
|
291
|
+
throw new Error("Expected child trace entry");
|
|
292
|
+
}
|
|
293
|
+
expect(childEntry.id).toBe("child");
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it("handles primitive string return", async () => {
|
|
297
|
+
const ctx = createMockCtx();
|
|
298
|
+
const $ = createStepBuilder({ ctx });
|
|
299
|
+
|
|
300
|
+
const result = await $.step({
|
|
301
|
+
id: "str",
|
|
302
|
+
execute: async () => "hello",
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
expect(result.ok).toBe(true);
|
|
306
|
+
if (!result.ok) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
expect(result.value).toBe("hello");
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it("handles primitive number return", async () => {
|
|
313
|
+
const ctx = createMockCtx();
|
|
314
|
+
const $ = createStepBuilder({ ctx });
|
|
315
|
+
|
|
316
|
+
const result = await $.step({
|
|
317
|
+
id: "num",
|
|
318
|
+
execute: async () => 42,
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
expect(result.ok).toBe(true);
|
|
322
|
+
if (!result.ok) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
expect(result.value).toBe(42);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it("onFinish receives the result and duration", async () => {
|
|
329
|
+
const onFinish = vi.fn();
|
|
330
|
+
const ctx = createMockCtx();
|
|
331
|
+
const $ = createStepBuilder({ ctx });
|
|
332
|
+
|
|
333
|
+
await $.step({
|
|
334
|
+
id: "finish-params",
|
|
335
|
+
execute: async () => ({ data: "test" }),
|
|
336
|
+
onFinish,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
expect(onFinish).toHaveBeenCalledTimes(1);
|
|
340
|
+
expect(onFinish).toHaveBeenCalledWith(
|
|
341
|
+
expect.objectContaining({
|
|
342
|
+
id: "finish-params",
|
|
343
|
+
result: { data: "test" },
|
|
344
|
+
duration: expect.any(Number),
|
|
345
|
+
}),
|
|
346
|
+
);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it("onError receives the error and step id", async () => {
|
|
350
|
+
const onError = vi.fn();
|
|
351
|
+
const ctx = createMockCtx();
|
|
352
|
+
const $ = createStepBuilder({ ctx });
|
|
353
|
+
|
|
354
|
+
await $.step({
|
|
355
|
+
id: "error-params",
|
|
356
|
+
execute: async () => {
|
|
357
|
+
throw new Error("test error");
|
|
358
|
+
},
|
|
359
|
+
onError,
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
expect(onError).toHaveBeenCalledTimes(1);
|
|
363
|
+
expect(onError).toHaveBeenCalledWith(
|
|
364
|
+
expect.objectContaining({
|
|
365
|
+
id: "error-params",
|
|
366
|
+
error: expect.any(Error),
|
|
367
|
+
}),
|
|
368
|
+
);
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it("handles null return value", async () => {
|
|
372
|
+
const ctx = createMockCtx();
|
|
373
|
+
const $ = createStepBuilder({ ctx });
|
|
374
|
+
|
|
375
|
+
const result = await $.step({
|
|
376
|
+
id: "null-return",
|
|
377
|
+
execute: async () => null,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
expect(result.ok).toBe(true);
|
|
381
|
+
if (!result.ok) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
expect(result.value).toBeNull();
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it("handles undefined return value", async () => {
|
|
388
|
+
const ctx = createMockCtx();
|
|
389
|
+
const $ = createStepBuilder({ ctx });
|
|
390
|
+
|
|
391
|
+
const result = await $.step({
|
|
392
|
+
id: "void-return",
|
|
393
|
+
execute: async () => undefined,
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
expect(result.ok).toBe(true);
|
|
397
|
+
if (!result.ok) {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
expect(result.value).toBeUndefined();
|
|
401
|
+
});
|
|
402
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { StepBuilder } from "@/core/agents/flow/steps/builder.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for `$.step()` — execute and register a unit of work.
|
|
5
|
+
*
|
|
6
|
+
* @typeParam T - The return type of the step's execute function.
|
|
7
|
+
*/
|
|
8
|
+
export interface StepConfig<T> {
|
|
9
|
+
/**
|
|
10
|
+
* Unique step identifier.
|
|
11
|
+
*
|
|
12
|
+
* Appears in the execution trace, hook events, and error messages.
|
|
13
|
+
*/
|
|
14
|
+
id: string;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Execute the step's logic.
|
|
18
|
+
*
|
|
19
|
+
* @param params - Execution parameters.
|
|
20
|
+
* @param params.$ - The step builder for nesting further operations.
|
|
21
|
+
* @returns The step result.
|
|
22
|
+
*/
|
|
23
|
+
execute: (params: { $: StepBuilder }) => Promise<T>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Hook: fires when this step starts.
|
|
27
|
+
*
|
|
28
|
+
* @param event - Event containing the step id.
|
|
29
|
+
* @param event.id - The step's unique identifier.
|
|
30
|
+
*/
|
|
31
|
+
onStart?: (event: { id: string }) => void | Promise<void>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Hook: fires when this step finishes successfully.
|
|
35
|
+
*
|
|
36
|
+
* @param event - Event containing the step id, result, and duration.
|
|
37
|
+
* @param event.id - The step's unique identifier.
|
|
38
|
+
* @param event.result - The value returned by `execute`.
|
|
39
|
+
* @param event.duration - Wall-clock time in milliseconds.
|
|
40
|
+
*/
|
|
41
|
+
onFinish?: (event: { id: string; result: T; duration: number }) => void | Promise<void>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Hook: fires when this step encounters an error.
|
|
45
|
+
*
|
|
46
|
+
* @param event - Event containing the step id and error.
|
|
47
|
+
* @param event.id - The step's unique identifier.
|
|
48
|
+
* @param event.error - The error that occurred.
|
|
49
|
+
*/
|
|
50
|
+
onError?: (event: { id: string; error: Error }) => void | Promise<void>;
|
|
51
|
+
}
|