@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.
Files changed (153) hide show
  1. package/.generated/req.txt +1 -0
  2. package/.turbo/turbo-build.log +21 -0
  3. package/.turbo/turbo-test$colon$coverage.log +109 -0
  4. package/.turbo/turbo-test.log +141 -0
  5. package/.turbo/turbo-typecheck.log +4 -0
  6. package/CHANGELOG.md +16 -0
  7. package/ISSUES.md +540 -0
  8. package/LICENSE +21 -0
  9. package/README.md +128 -0
  10. package/banner.svg +97 -0
  11. package/coverage/lcov-report/base.css +224 -0
  12. package/coverage/lcov-report/block-navigation.js +87 -0
  13. package/coverage/lcov-report/core/agents/base/agent.ts.html +1705 -0
  14. package/coverage/lcov-report/core/agents/base/index.html +146 -0
  15. package/coverage/lcov-report/core/agents/base/output.ts.html +256 -0
  16. package/coverage/lcov-report/core/agents/base/utils.ts.html +694 -0
  17. package/coverage/lcov-report/core/agents/flow/engine.ts.html +928 -0
  18. package/coverage/lcov-report/core/agents/flow/flow-agent.ts.html +1462 -0
  19. package/coverage/lcov-report/core/agents/flow/index.html +146 -0
  20. package/coverage/lcov-report/core/agents/flow/messages.ts.html +508 -0
  21. package/coverage/lcov-report/core/agents/flow/steps/factory.ts.html +1975 -0
  22. package/coverage/lcov-report/core/agents/flow/steps/index.html +116 -0
  23. package/coverage/lcov-report/core/index.html +131 -0
  24. package/coverage/lcov-report/core/logger.ts.html +541 -0
  25. package/coverage/lcov-report/core/models/providers/index.html +116 -0
  26. package/coverage/lcov-report/core/models/providers/openai.ts.html +337 -0
  27. package/coverage/lcov-report/core/provider/index.html +131 -0
  28. package/coverage/lcov-report/core/provider/provider.ts.html +346 -0
  29. package/coverage/lcov-report/core/provider/usage.ts.html +376 -0
  30. package/coverage/lcov-report/core/tool.ts.html +577 -0
  31. package/coverage/lcov-report/favicon.png +0 -0
  32. package/coverage/lcov-report/index.html +221 -0
  33. package/coverage/lcov-report/lib/hooks.ts.html +262 -0
  34. package/coverage/lcov-report/lib/index.html +161 -0
  35. package/coverage/lcov-report/lib/middleware.ts.html +274 -0
  36. package/coverage/lcov-report/lib/runnable.ts.html +151 -0
  37. package/coverage/lcov-report/lib/trace.ts.html +520 -0
  38. package/coverage/lcov-report/prettify.css +1 -0
  39. package/coverage/lcov-report/prettify.js +2 -0
  40. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  41. package/coverage/lcov-report/sorter.js +210 -0
  42. package/coverage/lcov-report/utils/attempt.ts.html +199 -0
  43. package/coverage/lcov-report/utils/error.ts.html +421 -0
  44. package/coverage/lcov-report/utils/index.html +176 -0
  45. package/coverage/lcov-report/utils/resolve.ts.html +208 -0
  46. package/coverage/lcov-report/utils/result.ts.html +538 -0
  47. package/coverage/lcov-report/utils/zod.ts.html +178 -0
  48. package/coverage/lcov.info +1566 -0
  49. package/dist/index.d.mts +2883 -0
  50. package/dist/index.d.mts.map +1 -0
  51. package/dist/index.mjs +2312 -0
  52. package/dist/index.mjs.map +1 -0
  53. package/docs/core/agent.md +231 -0
  54. package/docs/core/hooks.md +95 -0
  55. package/docs/core/overview.md +87 -0
  56. package/docs/core/step.md +279 -0
  57. package/docs/core/tools.md +98 -0
  58. package/docs/core/workflow.md +235 -0
  59. package/docs/guides/create-agent.md +224 -0
  60. package/docs/guides/create-tool.md +137 -0
  61. package/docs/guides/create-workflow.md +374 -0
  62. package/docs/overview.md +244 -0
  63. package/docs/provider/models.md +55 -0
  64. package/docs/provider/overview.md +106 -0
  65. package/docs/provider/usage.md +100 -0
  66. package/docs/research/experimental-context.md +167 -0
  67. package/docs/research/gap-analysis.md +86 -0
  68. package/docs/research/prepare-step-and-active-tools.md +138 -0
  69. package/docs/research/sub-agent-model.md +249 -0
  70. package/docs/troubleshooting.md +60 -0
  71. package/logo.svg +17 -0
  72. package/models.config.json +18 -0
  73. package/package.json +60 -0
  74. package/scripts/generate-models.ts +324 -0
  75. package/src/core/agents/base/agent.test.ts +1522 -0
  76. package/src/core/agents/base/agent.ts +547 -0
  77. package/src/core/agents/base/output.test.ts +93 -0
  78. package/src/core/agents/base/output.ts +57 -0
  79. package/src/core/agents/base/types.test-d.ts +69 -0
  80. package/src/core/agents/base/types.ts +503 -0
  81. package/src/core/agents/base/utils.test.ts +397 -0
  82. package/src/core/agents/base/utils.ts +197 -0
  83. package/src/core/agents/flow/engine.test.ts +452 -0
  84. package/src/core/agents/flow/engine.ts +281 -0
  85. package/src/core/agents/flow/flow-agent.test.ts +1027 -0
  86. package/src/core/agents/flow/flow-agent.ts +473 -0
  87. package/src/core/agents/flow/messages.test.ts +198 -0
  88. package/src/core/agents/flow/messages.ts +141 -0
  89. package/src/core/agents/flow/steps/agent.test.ts +280 -0
  90. package/src/core/agents/flow/steps/agent.ts +87 -0
  91. package/src/core/agents/flow/steps/all.test.ts +300 -0
  92. package/src/core/agents/flow/steps/all.ts +73 -0
  93. package/src/core/agents/flow/steps/builder.ts +124 -0
  94. package/src/core/agents/flow/steps/each.test.ts +257 -0
  95. package/src/core/agents/flow/steps/each.ts +61 -0
  96. package/src/core/agents/flow/steps/factory.test-d.ts +50 -0
  97. package/src/core/agents/flow/steps/factory.test.ts +1025 -0
  98. package/src/core/agents/flow/steps/factory.ts +645 -0
  99. package/src/core/agents/flow/steps/map.test.ts +273 -0
  100. package/src/core/agents/flow/steps/map.ts +75 -0
  101. package/src/core/agents/flow/steps/race.test.ts +290 -0
  102. package/src/core/agents/flow/steps/race.ts +59 -0
  103. package/src/core/agents/flow/steps/reduce.test.ts +310 -0
  104. package/src/core/agents/flow/steps/reduce.ts +73 -0
  105. package/src/core/agents/flow/steps/result.ts +27 -0
  106. package/src/core/agents/flow/steps/step.test.ts +402 -0
  107. package/src/core/agents/flow/steps/step.ts +51 -0
  108. package/src/core/agents/flow/steps/while.test.ts +283 -0
  109. package/src/core/agents/flow/steps/while.ts +75 -0
  110. package/src/core/agents/flow/types.ts +348 -0
  111. package/src/core/logger.test.ts +163 -0
  112. package/src/core/logger.ts +152 -0
  113. package/src/core/models/index.test.ts +137 -0
  114. package/src/core/models/index.ts +152 -0
  115. package/src/core/models/providers/openai.ts +84 -0
  116. package/src/core/provider/provider.test.ts +128 -0
  117. package/src/core/provider/provider.ts +99 -0
  118. package/src/core/provider/types.ts +98 -0
  119. package/src/core/provider/usage.test.ts +304 -0
  120. package/src/core/provider/usage.ts +97 -0
  121. package/src/core/tool.test.ts +65 -0
  122. package/src/core/tool.ts +164 -0
  123. package/src/core/types.ts +66 -0
  124. package/src/index.ts +95 -0
  125. package/src/lib/context.test.ts +86 -0
  126. package/src/lib/context.ts +49 -0
  127. package/src/lib/hooks.test.ts +102 -0
  128. package/src/lib/hooks.ts +59 -0
  129. package/src/lib/middleware.test.ts +122 -0
  130. package/src/lib/middleware.ts +63 -0
  131. package/src/lib/runnable.test.ts +41 -0
  132. package/src/lib/runnable.ts +22 -0
  133. package/src/lib/trace.test.ts +291 -0
  134. package/src/lib/trace.ts +145 -0
  135. package/src/models/index.ts +123 -0
  136. package/src/models/providers/index.ts +15 -0
  137. package/src/models/providers/openai.ts +84 -0
  138. package/src/testing/context.ts +32 -0
  139. package/src/testing/index.ts +2 -0
  140. package/src/testing/logger.ts +19 -0
  141. package/src/utils/attempt.test.ts +127 -0
  142. package/src/utils/attempt.ts +38 -0
  143. package/src/utils/error.test.ts +179 -0
  144. package/src/utils/error.ts +112 -0
  145. package/src/utils/resolve.test.ts +38 -0
  146. package/src/utils/resolve.ts +41 -0
  147. package/src/utils/result.test.ts +79 -0
  148. package/src/utils/result.ts +151 -0
  149. package/src/utils/zod.test.ts +69 -0
  150. package/src/utils/zod.ts +31 -0
  151. package/tsconfig.json +25 -0
  152. package/tsdown.config.ts +15 -0
  153. package/vitest.config.ts +46 -0
@@ -0,0 +1,141 @@
1
+ import type { Message } from "@/core/agents/base/types.js";
2
+ import { safeStringify } from "@/utils/error.js";
3
+
4
+ /**
5
+ * Build the `toolCallId` for a step.
6
+ *
7
+ * Combines the step id with the global step index to produce a unique
8
+ * identifier that correlates tool-call and tool-result messages.
9
+ *
10
+ * @param stepId - The step's user-provided id.
11
+ * @param index - The step's global index within the flow.
12
+ * @returns A unique tool call identifier.
13
+ */
14
+ export function buildToolCallId(stepId: string, index: number): string {
15
+ return `${stepId}-${index}`;
16
+ }
17
+
18
+ /**
19
+ * Create an assistant message containing a synthetic tool-call part.
20
+ *
21
+ * Emitted when a `$` step starts execution. The `input` field captures
22
+ * the step's input snapshot (or `{}` when no input is available).
23
+ *
24
+ * @param toolCallId - Unique tool call identifier.
25
+ * @param toolName - The step id used as the tool name.
26
+ * @param args - The step's input snapshot.
27
+ * @returns A `Message` with role `assistant` and a `tool-call` content part.
28
+ */
29
+ export function createToolCallMessage(
30
+ toolCallId: string,
31
+ toolName: string,
32
+ args: unknown,
33
+ ): Message {
34
+ return {
35
+ role: "assistant",
36
+ content: [
37
+ {
38
+ type: "tool-call",
39
+ toolCallId,
40
+ toolName,
41
+ input: args ?? {},
42
+ },
43
+ ],
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Create a tool message containing a synthetic tool-result part.
49
+ *
50
+ * Emitted when a `$` step finishes execution. The `result` field captures
51
+ * the step's output. When `isError` is true, the result represents an
52
+ * error message.
53
+ *
54
+ * @param toolCallId - Unique tool call identifier (must match the paired tool-call).
55
+ * @param toolName - The step id used as the tool name.
56
+ * @param result - The step's output snapshot.
57
+ * @param isError - Whether this result represents an error.
58
+ * @returns A `Message` with role `tool` and a `tool-result` content part.
59
+ */
60
+ export function createToolResultMessage(
61
+ toolCallId: string,
62
+ toolName: string,
63
+ result: unknown,
64
+ isError?: boolean,
65
+ ): Message {
66
+ // Synthetic tool-result for flow step tracking — not consumed by the AI SDK
67
+ return {
68
+ role: "tool",
69
+ content: [
70
+ {
71
+ type: "tool-result",
72
+ toolCallId,
73
+ toolName,
74
+ output: result ?? {},
75
+ ...(isError ? { isError: true } : {}),
76
+ },
77
+ ],
78
+ } as Message;
79
+ }
80
+
81
+ /**
82
+ * Safely serialize a value to a string for message content.
83
+ *
84
+ * Returns the value as-is when it's already a string. Otherwise
85
+ * delegates to {@link safeStringify} which handles circular refs,
86
+ * Maps, Sets, bigints, and other non-JSON-serializable types.
87
+ *
88
+ * @param value - The value to serialize.
89
+ * @returns A string representation of the value.
90
+ */
91
+ function serializeMessageContent(value: unknown): string {
92
+ if (typeof value === "string") {
93
+ return value;
94
+ }
95
+ return safeStringify(value ?? null);
96
+ }
97
+
98
+ /**
99
+ * Create a user message from flow agent input.
100
+ *
101
+ * This is the first message in the flow's message array, representing
102
+ * the input passed to `flowAgent.generate()` or `flowAgent.stream()`.
103
+ *
104
+ * @param input - The flow agent input.
105
+ * @returns A `Message` with role `user`.
106
+ */
107
+ export function createUserMessage(input: unknown): Message {
108
+ return { role: "user", content: serializeMessageContent(input) };
109
+ }
110
+
111
+ /**
112
+ * Create a final assistant message from flow agent output.
113
+ *
114
+ * This is the last message in the flow's message array, representing
115
+ * the validated output returned by the handler.
116
+ *
117
+ * @param output - The flow agent output.
118
+ * @returns A `Message` with role `assistant`.
119
+ */
120
+ export function createAssistantMessage(output: unknown): Message {
121
+ return { role: "assistant", content: serializeMessageContent(output) };
122
+ }
123
+
124
+ /**
125
+ * Collect text content from assistant messages in the message array.
126
+ *
127
+ * Used for flow agents without an output schema — the output is
128
+ * the concatenated text from sub-agent responses. Only considers
129
+ * messages with string content (tool-call messages have array content
130
+ * and are skipped).
131
+ *
132
+ * @param messages - The flow's message array.
133
+ * @returns The concatenated assistant text, trimmed.
134
+ */
135
+ export function collectTextFromMessages(messages: readonly Message[]): string {
136
+ return messages
137
+ .filter((m) => m.role === "assistant" && typeof m.content === "string")
138
+ .map((m) => m.content as string)
139
+ .join("\n")
140
+ .trim();
141
+ }
@@ -0,0 +1,280 @@
1
+ import { match } from "ts-pattern";
2
+ import { describe, expect, it, vi } from "vitest";
3
+
4
+ import type { Agent, GenerateResult } from "@/core/agents/base/types.js";
5
+ import { createStepBuilder } from "@/core/agents/flow/steps/factory.js";
6
+ import { createMockCtx } from "@/testing/index.js";
7
+ import type { Result } from "@/utils/result.js";
8
+
9
+ const MOCK_USAGE = {
10
+ inputTokens: 100,
11
+ outputTokens: 50,
12
+ totalTokens: 150,
13
+ cacheReadTokens: 0,
14
+ cacheWriteTokens: 0,
15
+ reasoningTokens: 0,
16
+ };
17
+
18
+ function mockAgent(result: Result<Pick<GenerateResult, "output" | "messages">>): Agent<string> {
19
+ const resolved: Result<GenerateResult> = match(result)
20
+ .with({ ok: true }, (r) => ({ ...r, usage: MOCK_USAGE, finishReason: "stop" as const }))
21
+ .otherwise((r) => r);
22
+ return {
23
+ generate: vi.fn(async () => resolved),
24
+ stream: vi.fn(),
25
+ fn: vi.fn(),
26
+ } as unknown as Agent<string>;
27
+ }
28
+
29
+ describe("agent()", () => {
30
+ it("unwraps successful agent result into StepResult", async () => {
31
+ const ctx = createMockCtx();
32
+ const $ = createStepBuilder({ ctx });
33
+ const agent = mockAgent({
34
+ ok: true,
35
+ output: "hello",
36
+ messages: [],
37
+ });
38
+
39
+ const result = await $.agent({ id: "ag", agent, input: "test" });
40
+
41
+ expect(result.ok).toBe(true);
42
+ if (!result.ok) {
43
+ return;
44
+ }
45
+ expect(result.value.output).toBe("hello");
46
+ expect(result.value.messages).toEqual([]);
47
+ expect(result.value.usage).toEqual(MOCK_USAGE);
48
+ expect(result.value.finishReason).toBe("stop");
49
+ expect(result.step.type).toBe("agent");
50
+ });
51
+
52
+ it("converts agent error result into StepError", async () => {
53
+ const ctx = createMockCtx();
54
+ const $ = createStepBuilder({ ctx });
55
+ const agent = mockAgent({
56
+ ok: false,
57
+ error: { code: "AGENT_ERROR", message: "agent failed", cause: new Error("root") },
58
+ });
59
+
60
+ const result = await $.agent({ id: "ag-err", agent, input: "test" });
61
+
62
+ expect(result.ok).toBe(false);
63
+ if (result.ok) {
64
+ return;
65
+ }
66
+ expect(result.error.code).toBe("STEP_ERROR");
67
+ expect(result.error.stepId).toBe("ag-err");
68
+ });
69
+
70
+ it("throws the cause error from agent error result", async () => {
71
+ const ctx = createMockCtx();
72
+ const $ = createStepBuilder({ ctx });
73
+ const rootCause = new Error("root cause");
74
+ const agent = mockAgent({
75
+ ok: false,
76
+ error: { code: "AGENT_ERROR", message: "agent failed", cause: rootCause },
77
+ });
78
+
79
+ const result = await $.agent({ id: "ag-cause", agent, input: "test" });
80
+
81
+ expect(result.ok).toBe(false);
82
+ if (result.ok) {
83
+ return;
84
+ }
85
+ expect(result.error.cause).toBe(rootCause);
86
+ });
87
+
88
+ it("creates new Error from message when no cause is provided", async () => {
89
+ const ctx = createMockCtx();
90
+ const $ = createStepBuilder({ ctx });
91
+ const agent = mockAgent({
92
+ ok: false,
93
+ error: { code: "AGENT_ERROR", message: "no cause" },
94
+ });
95
+
96
+ const result = await $.agent({ id: "ag-no-cause", agent, input: "test" });
97
+
98
+ expect(result.ok).toBe(false);
99
+ if (result.ok) {
100
+ return;
101
+ }
102
+ expect(result.error.message).toBe("no cause");
103
+ });
104
+
105
+ it("calls agent.generate with input and config", async () => {
106
+ const ctx = createMockCtx();
107
+ const $ = createStepBuilder({ ctx });
108
+ const agent = mockAgent({ ok: true, output: "hi", messages: [] });
109
+ const config = { signal: new AbortController().signal };
110
+
111
+ await $.agent({ id: "ag-cfg", agent, input: "hello", config });
112
+
113
+ expect(agent.generate).toHaveBeenCalledWith(
114
+ "hello",
115
+ expect.objectContaining({ signal: config.signal, logger: expect.any(Object) }),
116
+ );
117
+ });
118
+
119
+ it("propagates ctx.signal to agent when no user signal is provided", async () => {
120
+ const controller = new AbortController();
121
+ const ctx = createMockCtx({ signal: controller.signal });
122
+ const $ = createStepBuilder({ ctx });
123
+ const agent = mockAgent({ ok: true, output: "hi", messages: [] });
124
+
125
+ await $.agent({ id: "ag-ctx-signal", agent, input: "test" });
126
+
127
+ expect(agent.generate).toHaveBeenCalledWith(
128
+ "test",
129
+ expect.objectContaining({ signal: controller.signal }),
130
+ );
131
+ });
132
+
133
+ it("user-provided config.signal takes precedence over ctx.signal", async () => {
134
+ const ctxController = new AbortController();
135
+ const userController = new AbortController();
136
+ const ctx = createMockCtx({ signal: ctxController.signal });
137
+ const $ = createStepBuilder({ ctx });
138
+ const agent = mockAgent({ ok: true, output: "hi", messages: [] });
139
+
140
+ await $.agent({
141
+ id: "ag-user-signal",
142
+ agent,
143
+ input: "test",
144
+ config: { signal: userController.signal },
145
+ });
146
+
147
+ expect(agent.generate).toHaveBeenCalledWith(
148
+ "test",
149
+ expect.objectContaining({ signal: userController.signal }),
150
+ );
151
+ });
152
+
153
+ it("records input in trace", async () => {
154
+ const ctx = createMockCtx();
155
+ const $ = createStepBuilder({ ctx });
156
+ const agent = mockAgent({ ok: true, output: "hi", messages: [] });
157
+
158
+ await $.agent({ id: "ag-trace", agent, input: "my-input" });
159
+
160
+ const traceEntry = ctx.trace[0];
161
+ if (traceEntry === undefined) {
162
+ throw new Error("Expected trace entry");
163
+ }
164
+ expect(traceEntry.input).toBe("my-input");
165
+ expect(traceEntry.type).toBe("agent");
166
+ });
167
+
168
+ it("fires onStart and onFinish hooks", async () => {
169
+ const order: string[] = [];
170
+ const ctx = createMockCtx();
171
+ const $ = createStepBuilder({ ctx });
172
+ const agent = mockAgent({ ok: true, output: "done", messages: [] });
173
+
174
+ await $.agent({
175
+ id: "ag-hooks",
176
+ agent,
177
+ input: "test",
178
+ onStart: () => {
179
+ order.push("onStart");
180
+ },
181
+ onFinish: () => {
182
+ order.push("onFinish");
183
+ },
184
+ });
185
+
186
+ expect(order).toEqual(["onStart", "onFinish"]);
187
+ });
188
+
189
+ it("fires onError hook on failure", async () => {
190
+ const onError = vi.fn();
191
+ const ctx = createMockCtx();
192
+ const $ = createStepBuilder({ ctx });
193
+ const agent = mockAgent({
194
+ ok: false,
195
+ error: { code: "AGENT_ERROR", message: "failed" },
196
+ });
197
+
198
+ await $.agent({
199
+ id: "ag-onerror",
200
+ agent,
201
+ input: "test",
202
+ onError,
203
+ });
204
+
205
+ expect(onError).toHaveBeenCalledTimes(1);
206
+ expect(onError).toHaveBeenCalledWith(
207
+ expect.objectContaining({
208
+ id: "ag-onerror",
209
+ error: expect.any(Error),
210
+ }),
211
+ );
212
+ });
213
+
214
+ it("onFinish receives the GenerateResult", async () => {
215
+ const onFinish = vi.fn();
216
+ const ctx = createMockCtx();
217
+ const $ = createStepBuilder({ ctx });
218
+ const agent = mockAgent({ ok: true, output: "result-text", messages: [] });
219
+
220
+ await $.agent({
221
+ id: "ag-finish-result",
222
+ agent,
223
+ input: "test",
224
+ onFinish,
225
+ });
226
+
227
+ expect(onFinish).toHaveBeenCalledWith(
228
+ expect.objectContaining({
229
+ id: "ag-finish-result",
230
+ result: expect.objectContaining({
231
+ output: "result-text",
232
+ messages: [],
233
+ }),
234
+ duration: expect.any(Number),
235
+ }),
236
+ );
237
+ });
238
+
239
+ it("passes scoped logger to agent.generate", async () => {
240
+ const ctx = createMockCtx();
241
+ const $ = createStepBuilder({ ctx });
242
+ const agent = mockAgent({ ok: true, output: "hi", messages: [] });
243
+
244
+ await $.agent({ id: "ag-logger", agent, input: "test" });
245
+
246
+ expect(agent.generate).toHaveBeenCalledWith(
247
+ "test",
248
+ expect.objectContaining({ logger: expect.any(Object) }),
249
+ );
250
+ expect(ctx.log.child).toHaveBeenCalledWith({ stepId: "ag-logger" });
251
+ });
252
+
253
+ it("handles agent returning empty messages array", async () => {
254
+ const ctx = createMockCtx();
255
+ const $ = createStepBuilder({ ctx });
256
+ const agent = mockAgent({ ok: true, output: "text", messages: [] });
257
+
258
+ const result = await $.agent({ id: "ag-empty-msgs", agent, input: "test" });
259
+
260
+ expect(result.ok).toBe(true);
261
+ if (!result.ok) {
262
+ return;
263
+ }
264
+ expect(result.value.messages).toEqual([]);
265
+ });
266
+
267
+ it("records usage on trace entry", async () => {
268
+ const ctx = createMockCtx();
269
+ const $ = createStepBuilder({ ctx });
270
+ const agent = mockAgent({ ok: true, output: "hi", messages: [] });
271
+
272
+ await $.agent({ id: "ag-trace-usage", agent, input: "test" });
273
+
274
+ const traceEntry = ctx.trace[0];
275
+ if (traceEntry === undefined) {
276
+ throw new Error("Expected trace entry");
277
+ }
278
+ expect(traceEntry.usage).toEqual(MOCK_USAGE);
279
+ });
280
+ });
@@ -0,0 +1,87 @@
1
+ import type { AgentOverrides, GenerateResult } from "@/core/agents/base/types.js";
2
+ import type { Runnable } from "@/core/types.js";
3
+
4
+ /**
5
+ * Configuration for `$.agent()` — execute an agent call as a tracked operation.
6
+ *
7
+ * The `input` field matches whatever the agent accepts — typed `TInput`
8
+ * if the agent has a schema, or `string | Message[]` for simple agents.
9
+ *
10
+ * @typeParam TInput - The agent's input type.
11
+ */
12
+ export interface AgentStepConfig<TInput> {
13
+ /**
14
+ * Unique step identifier.
15
+ *
16
+ * Appears in the execution trace, hook events, and error messages.
17
+ */
18
+ id: string;
19
+
20
+ /**
21
+ * The agent to invoke.
22
+ *
23
+ * The framework calls `agent.generate()` internally with the
24
+ * provided `input` and optional `config` overrides.
25
+ */
26
+ agent: Runnable<TInput>;
27
+
28
+ /**
29
+ * Input to pass to the agent.
30
+ *
31
+ * Same as what you'd pass to `agent.generate()` — typed `TInput`
32
+ * for agents with an input schema, or `string | Message[]` for
33
+ * simple agents.
34
+ */
35
+ input: TInput;
36
+
37
+ /**
38
+ * Optional inline overrides for this agent call.
39
+ *
40
+ * Accepts the same fields as `AgentOverrides` — model, output,
41
+ * tools, hooks, etc.
42
+ */
43
+ config?: AgentOverrides;
44
+
45
+ /**
46
+ * When `true`, call `agent.stream()` instead of `agent.generate()`
47
+ * and pipe the agent's text output through the parent flow's stream.
48
+ *
49
+ * Only has an effect when the flow agent is streaming (i.e., when
50
+ * a stream writer is available). Falls back to `agent.generate()`
51
+ * when the flow agent is not streaming.
52
+ *
53
+ * @default false
54
+ */
55
+ stream?: boolean;
56
+
57
+ /**
58
+ * Hook: fires when this agent step starts.
59
+ *
60
+ * @param event - Event containing the step id.
61
+ * @param event.id - The step's unique identifier.
62
+ */
63
+ onStart?: (event: { id: string }) => void | Promise<void>;
64
+
65
+ /**
66
+ * Hook: fires when this agent step finishes.
67
+ *
68
+ * @param event - Event containing the step id, result, and duration.
69
+ * @param event.id - The step's unique identifier.
70
+ * @param event.result - The agent's `GenerateResult`.
71
+ * @param event.duration - Wall-clock time in milliseconds.
72
+ */
73
+ onFinish?: (event: {
74
+ id: string;
75
+ result: GenerateResult;
76
+ duration: number;
77
+ }) => void | Promise<void>;
78
+
79
+ /**
80
+ * Hook: fires when this agent step encounters an error.
81
+ *
82
+ * @param event - Event containing the step id and error.
83
+ * @param event.id - The step's unique identifier.
84
+ * @param event.error - The error that occurred.
85
+ */
86
+ onError?: (event: { id: string; error: Error }) => void | Promise<void>;
87
+ }