@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
package/dist/index.mjs ADDED
@@ -0,0 +1,2312 @@
1
+ import { Output, generateText, stepCountIs, streamText, tool as tool$1, wrapLanguageModel, zodSchema } from "ai";
2
+ import { attempt, groupBy, isError, isFunction, isMap, isNil, isPrimitive, isSet, isString, sumBy } from "es-toolkit";
3
+ import { has, isObject } from "es-toolkit/compat";
4
+ import { P, match } from "ts-pattern";
5
+ import { z } from "zod";
6
+ import { createOpenRouter as createOpenRouter$1 } from "@openrouter/ai-sdk-provider";
7
+ //#region src/core/tool.ts
8
+ /**
9
+ * Create a tool for AI agent function calling.
10
+ *
11
+ * Wraps the AI SDK's `tool()` helper. The `inputSchema` (and optional
12
+ * `outputSchema`) are wrapped via `zodSchema()` so the AI SDK can
13
+ * convert them to JSON Schema and validate model I/O at runtime.
14
+ *
15
+ * @see https://ai-sdk.dev/docs/reference/ai-sdk-core/tool
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * const fetchPage = tool({
20
+ * description: 'Fetch the contents of a web page by URL',
21
+ * inputSchema: z.object({
22
+ * url: z.url(),
23
+ * }),
24
+ * execute: async ({ url }) => {
25
+ * const res = await fetch(url)
26
+ * return {
27
+ * url,
28
+ * status: res.status,
29
+ * body: await res.text(),
30
+ * }
31
+ * },
32
+ * })
33
+ * ```
34
+ */
35
+ function tool(config) {
36
+ const resolvedOutputSchema = resolveOutputSchema(config.outputSchema);
37
+ const result = {
38
+ description: config.description,
39
+ title: config.title,
40
+ inputSchema: zodSchema(config.inputSchema),
41
+ outputSchema: resolvedOutputSchema,
42
+ inputExamples: config.inputExamples,
43
+ execute: async (data) => config.execute(data)
44
+ };
45
+ assertTool(result);
46
+ return tool$1(result);
47
+ }
48
+ /**
49
+ * Resolve an optional Zod output schema into a zodSchema-wrapped value.
50
+ *
51
+ * @private
52
+ */
53
+ function resolveOutputSchema(schema) {
54
+ return match(schema).with(P.nullish, () => void 0).otherwise((value) => zodSchema(value));
55
+ }
56
+ /**
57
+ * Runtime assertion that narrows `value` to `Tool<TInput, TOutput>`.
58
+ *
59
+ * Validates structural shape at runtime — `inputSchema` is present and
60
+ * `execute` is a function. Generic type parameters (`TInput`, `TOutput`)
61
+ * are erased at runtime, so only the structural shape can be verified.
62
+ *
63
+ * This assertion exists because TypeScript cannot evaluate the AI SDK's
64
+ * `NeverOptional<TOutput>` conditional type when `TOutput` is an
65
+ * unresolved generic — a known limitation with higher-order conditional
66
+ * types. Using `asserts` is TypeScript's endorsed narrowing mechanism
67
+ * and provides a runtime safety net if the AI SDK's tool shape changes.
68
+ *
69
+ * @private
70
+ */
71
+ /* v8 ignore start -- defensive guard; tool() always constructs a valid object */
72
+ function assertTool(value) {
73
+ if (isNil(value) || !isObject(value)) throw new TypeError("Expected tool to be an object");
74
+ if (!has(value, "inputSchema")) throw new TypeError("Tool is missing required property: inputSchema");
75
+ if (!has(value, "execute") || !isFunction(value.execute)) throw new TypeError("Tool is missing required property: execute");
76
+ }
77
+ /* v8 ignore stop */
78
+ //#endregion
79
+ //#region src/utils/zod.ts
80
+ /**
81
+ * Convert a Zod schema to a JSON Schema object.
82
+ */
83
+ function toJsonSchema(schema) {
84
+ return z.toJSONSchema(schema);
85
+ }
86
+ /**
87
+ * Check if a Zod schema produces a JSON Schema with `type: 'array'`.
88
+ *
89
+ * Uses JSON Schema output rather than `instanceof` to correctly handle
90
+ * wrapped schemas (transforms, refinements, pipes).
91
+ */
92
+ function isZodArray(schema) {
93
+ return toJsonSchema(schema).type === "array";
94
+ }
95
+ //#endregion
96
+ //#region src/core/agents/base/output.ts
97
+ /**
98
+ * Resolve an `OutputParam` into an `OutputSpec`.
99
+ *
100
+ * If the value is already an `OutputSpec`, it is returned as-is.
101
+ * If it is a raw Zod schema:
102
+ * - `z.array(...)` → `Output.array({ element: innerSchema })`
103
+ * - Anything else → `Output.object({ schema })`
104
+ *
105
+ * @internal
106
+ */
107
+ function resolveOutput(output) {
108
+ return match("parseCompleteOutput" in output).with(true, () => output).otherwise(() => {
109
+ const schema = output;
110
+ return match(isZodArray(schema)).with(true, () => {
111
+ const def = schema._zod;
112
+ if (def != null && def.def.element != null) return Output.array({ element: def.def.element });
113
+ throw new Error("Failed to extract element schema from Zod array. Pass Output.array({ element: elementSchema }) explicitly.");
114
+ }).otherwise(() => Output.object({ schema }));
115
+ });
116
+ }
117
+ //#endregion
118
+ //#region src/core/provider/provider.ts
119
+ /**
120
+ * Create an OpenRouter provider instance with automatic API key resolution.
121
+ *
122
+ * Falls back to the `OPENROUTER_API_KEY` environment variable when
123
+ * no explicit `apiKey` is provided in the options.
124
+ *
125
+ * @param options - Provider settings forwarded to `@openrouter/ai-sdk-provider`.
126
+ * @returns A configured {@link OpenRouterProvider} instance.
127
+ *
128
+ * @example
129
+ * ```typescript
130
+ * const openrouter = createOpenRouter({ apiKey: 'sk-...' })
131
+ * const m = openrouter('openai/gpt-5.2-codex')
132
+ * ```
133
+ */
134
+ function createOpenRouter(options) {
135
+ const apiKey = resolveApiKey(options);
136
+ return createOpenRouter$1({
137
+ ...options,
138
+ apiKey
139
+ });
140
+ }
141
+ function resolveApiKey(options) {
142
+ if (options != null && options.apiKey != null) return options.apiKey;
143
+ return getOpenRouterApiKey();
144
+ }
145
+ /**
146
+ * Create a cached OpenRouter model resolver.
147
+ *
148
+ * The returned function caches the underlying provider and invalidates
149
+ * when the API key changes at runtime.
150
+ *
151
+ * @returns A function that resolves a model ID to a {@link LanguageModel}.
152
+ *
153
+ * @private
154
+ */
155
+ function createCachedOpenRouter() {
156
+ const cache = {
157
+ provider: void 0,
158
+ apiKey: void 0
159
+ };
160
+ return (modelId) => {
161
+ const apiKey = getOpenRouterApiKey();
162
+ if (!cache.provider || cache.apiKey !== apiKey) {
163
+ cache.provider = createOpenRouter$1({ apiKey });
164
+ cache.apiKey = apiKey;
165
+ }
166
+ return cache.provider(modelId);
167
+ };
168
+ }
169
+ /**
170
+ * Shorthand for creating a single OpenRouter language model.
171
+ *
172
+ * Resolves the API key from the environment and returns a ready-to-use
173
+ * {@link LanguageModel} that can be passed directly to AI SDK functions.
174
+ *
175
+ * The provider instance is cached at module scope and reused across
176
+ * calls. If `OPENROUTER_API_KEY` changes at runtime, the cache is
177
+ * invalidated and a new provider is created.
178
+ *
179
+ * @param modelId - An OpenRouter model identifier (e.g. `"openai/gpt-5.2-codex"`).
180
+ * @returns A configured {@link LanguageModel} instance.
181
+ *
182
+ * @example
183
+ * ```typescript
184
+ * const m = openrouter('openai/gpt-5.2-codex')
185
+ * ```
186
+ */
187
+ const openrouter = createCachedOpenRouter();
188
+ /**
189
+ * Read the OpenRouter API key from the environment.
190
+ *
191
+ * @throws {Error} If `OPENROUTER_API_KEY` is not set.
192
+ */
193
+ function getOpenRouterApiKey() {
194
+ const apiKey = process.env.OPENROUTER_API_KEY;
195
+ if (!apiKey) throw new Error("OPENROUTER_API_KEY environment variable is required. Set it in your .env file or environment.");
196
+ return apiKey;
197
+ }
198
+ //#endregion
199
+ //#region src/lib/runnable.ts
200
+ /**
201
+ * Symbol key for internal runnable metadata.
202
+ *
203
+ * Stored on Agent and Workflow objects to enable composition:
204
+ * `buildAITools()` reads this to wrap a Runnable as a delegatable
205
+ * tool in parent agents.
206
+ *
207
+ * @internal
208
+ */
209
+ const RUNNABLE_META = Symbol.for("agent-sdk:runnable-meta");
210
+ //#endregion
211
+ //#region src/core/agents/base/utils.ts
212
+ /**
213
+ * Resolve a display name for a sub-agent tool from its runnable
214
+ * metadata, falling back to the provided name.
215
+ *
216
+ * @param meta - The runnable metadata, or undefined if not available.
217
+ * @param fallback - The fallback name to use if metadata is missing.
218
+ * @returns The resolved tool name.
219
+ *
220
+ * @private
221
+ */
222
+ function resolveToolName(meta, fallback) {
223
+ if (meta != null && meta.name != null) return meta.name;
224
+ return fallback;
225
+ }
226
+ /**
227
+ * Resolve a {@link Model} to an AI SDK `LanguageModel`.
228
+ */
229
+ function resolveModel(ref) {
230
+ if (typeof ref === "string") return openrouter(ref);
231
+ return ref;
232
+ }
233
+ /**
234
+ * Merge `Tool` records and wrap subagent `Runnable` objects into AI SDK
235
+ * tool format for `generateText` / `streamText`.
236
+ *
237
+ * Tools created via `tool()` are already AI SDK tools and are
238
+ * passed through directly. Only subagents need wrapping.
239
+ *
240
+ * Parent tools are automatically forwarded to sub-agents so they
241
+ * can access the same capabilities (e.g. sandbox filesystem tools)
242
+ * without explicit injection at each call site.
243
+ */
244
+ function buildAITools(tools, agents) {
245
+ const hasTools = tools != null && Object.keys(tools).length > 0;
246
+ const hasAgents = agents != null && Object.keys(agents).length > 0;
247
+ if (!hasTools && !hasAgents) return;
248
+ const agentTools = agents ? Object.fromEntries(Object.entries(agents).map(([name, runnable]) => {
249
+ const meta = runnable[RUNNABLE_META];
250
+ const toolName = resolveToolName(meta, name);
251
+ return [`agent:${name}`, meta != null && meta.inputSchema != null ? tool$1({
252
+ description: `Delegate to ${toolName}`,
253
+ inputSchema: meta.inputSchema,
254
+ execute: async (input, { abortSignal }) => {
255
+ const r = await runnable.generate(input, {
256
+ signal: abortSignal,
257
+ tools
258
+ });
259
+ if (!r.ok) throw new Error(r.error.message);
260
+ return r.output;
261
+ }
262
+ }) : tool$1({
263
+ description: `Delegate to ${toolName}`,
264
+ inputSchema: z.object({ prompt: z.string().describe("The prompt to send") }),
265
+ execute: async (input, { abortSignal }) => {
266
+ const r = await runnable.generate(input.prompt, {
267
+ signal: abortSignal,
268
+ tools
269
+ });
270
+ if (!r.ok) throw new Error(r.error.message);
271
+ return r.output;
272
+ }
273
+ })];
274
+ })) : {};
275
+ return {
276
+ ...tools,
277
+ ...agentTools
278
+ };
279
+ }
280
+ /**
281
+ * Resolve the system prompt from config or override.
282
+ */
283
+ function resolveSystem(system, input) {
284
+ if (system == null) return;
285
+ if (typeof system === "function") return system({ input });
286
+ return system;
287
+ }
288
+ /**
289
+ * Build the prompt/messages from input based on mode (typed vs simple).
290
+ *
291
+ * Returns a discriminated object: either `{ prompt }` or `{ messages }`,
292
+ * never both — matching the AI SDK's `Prompt` union type.
293
+ */
294
+ function buildPrompt(input, config) {
295
+ return match({
296
+ hasInput: Boolean(config.input),
297
+ hasPrompt: Boolean(config.prompt)
298
+ }).with({
299
+ hasInput: true,
300
+ hasPrompt: false
301
+ }, () => {
302
+ throw new Error("Agent has `input` schema but no `prompt` function — both are required for typed mode");
303
+ }).with({
304
+ hasInput: false,
305
+ hasPrompt: true
306
+ }, () => {
307
+ throw new Error("Agent has `prompt` function but no `input` schema — both are required for typed mode");
308
+ }).with({
309
+ hasInput: true,
310
+ hasPrompt: true
311
+ }, () => {
312
+ const promptFn = config.prompt;
313
+ const built = promptFn({ input });
314
+ return match(typeof built === "string").with(true, () => ({ prompt: built })).otherwise(() => ({ messages: built }));
315
+ }).otherwise(() => match(typeof input === "string").with(true, () => ({ prompt: input })).otherwise(() => ({ messages: input })));
316
+ }
317
+ /**
318
+ * Convert AI SDK's `LanguageModelUsage` to our flat `TokenUsage`.
319
+ *
320
+ * Maps nested `inputTokenDetails` / `outputTokenDetails` to flat
321
+ * fields, resolving `undefined` → `0`.
322
+ *
323
+ * @param usage - The AI SDK usage object (from `totalUsage`).
324
+ * @returns A resolved {@link TokenUsage} with all fields as numbers.
325
+ */
326
+ function toTokenUsage(usage) {
327
+ const inputDetails = match(usage.inputTokenDetails).with(P.nonNullable, (d) => ({
328
+ cacheReadTokens: d.cacheReadTokens ?? 0,
329
+ cacheWriteTokens: d.cacheWriteTokens ?? 0
330
+ })).otherwise(() => ({
331
+ cacheReadTokens: 0,
332
+ cacheWriteTokens: 0
333
+ }));
334
+ const outputDetails = match(usage.outputTokenDetails).with(P.nonNullable, (d) => ({ reasoningTokens: d.reasoningTokens ?? 0 })).otherwise(() => ({ reasoningTokens: 0 }));
335
+ return {
336
+ inputTokens: usage.inputTokens ?? 0,
337
+ outputTokens: usage.outputTokens ?? 0,
338
+ totalTokens: usage.totalTokens ?? 0,
339
+ cacheReadTokens: inputDetails.cacheReadTokens,
340
+ cacheWriteTokens: inputDetails.cacheWriteTokens,
341
+ reasoningTokens: outputDetails.reasoningTokens
342
+ };
343
+ }
344
+ //#endregion
345
+ //#region src/core/logger.ts
346
+ /**
347
+ * Create a minimal console-based logger satisfying the {@link Logger} interface.
348
+ *
349
+ * Supports `child()` by merging bindings into a prefix object.
350
+ * Each log call prepends the accumulated bindings to the output.
351
+ *
352
+ * Used as the default when no pino-compatible logger is injected.
353
+ */
354
+ function createDefaultLogger(bindings) {
355
+ const prefix = bindings ?? {};
356
+ return {
357
+ debug(first, second) {
358
+ writeLog(prefix, "debug", first, second);
359
+ },
360
+ info(first, second) {
361
+ writeLog(prefix, "info", first, second);
362
+ },
363
+ warn(first, second) {
364
+ writeLog(prefix, "warn", first, second);
365
+ },
366
+ error(first, second) {
367
+ writeLog(prefix, "error", first, second);
368
+ },
369
+ child(childBindings) {
370
+ return createDefaultLogger({
371
+ ...prefix,
372
+ ...childBindings
373
+ });
374
+ }
375
+ };
376
+ }
377
+ /**
378
+ *
379
+ * @param bindings
380
+ * @param level
381
+ * @param first
382
+ * @param second
383
+ * @private
384
+ */
385
+ function writeLog(bindings, level, first, second) {
386
+ if (typeof first === "string") {
387
+ const meta = second;
388
+ console[level]({
389
+ ...bindings,
390
+ ...meta
391
+ }, first);
392
+ } else console[level]({
393
+ ...bindings,
394
+ ...first
395
+ }, second);
396
+ }
397
+ //#endregion
398
+ //#region src/lib/hooks.ts
399
+ const formatHookError = (err) => match(err).when((e) => e instanceof Error, (e) => e.message).otherwise((e) => String(e));
400
+ /**
401
+ * Wrap a nullable hook into a callback for `fireHooks`, avoiding
402
+ * optional chaining, ternaries, and non-null assertions.
403
+ *
404
+ * @param hookFn - The hook callback, or undefined if not configured.
405
+ * @param event - The event payload to pass to the hook.
406
+ * @returns A thunk that calls the hook with the event, or undefined.
407
+ *
408
+ * @private
409
+ */
410
+ function wrapHook(hookFn, event) {
411
+ if (hookFn !== void 0) return () => hookFn(event);
412
+ }
413
+ /**
414
+ * Run hook callbacks sequentially, logging errors at warn level.
415
+ *
416
+ * Unlike `attemptEachAsync`, this function surfaces errors via the
417
+ * logger so hook failures are visible in diagnostic output.
418
+ *
419
+ * @param log - Logger for warning about hook errors.
420
+ * @param handlers - Callbacks to execute in order. `undefined` entries are skipped.
421
+ */
422
+ async function fireHooks(log, ...handlers) {
423
+ for (const h of handlers) if (h != null) try {
424
+ await h();
425
+ } catch (err) {
426
+ const errorMessage = formatHookError(err);
427
+ log.warn("hook error", { error: errorMessage });
428
+ }
429
+ }
430
+ //#endregion
431
+ //#region src/lib/middleware.ts
432
+ /**
433
+ * Wrap a language model with middleware.
434
+ *
435
+ * In development (`NODE_ENV === 'development'`), the AI SDK devtools
436
+ * middleware is appended automatically. Any additional middleware
437
+ * provided in the options is applied first (outermost).
438
+ *
439
+ * @param options - The model and optional middleware configuration.
440
+ * @returns A wrapped language model with middleware applied.
441
+ */
442
+ async function withModelMiddleware(options) {
443
+ const useDevtools = options.devtools === true || options.devtools !== false && process.env.NODE_ENV === "development";
444
+ const defaultMiddleware = [];
445
+ if (useDevtools) {
446
+ const { devToolsMiddleware } = await import("@ai-sdk/devtools");
447
+ defaultMiddleware.push(devToolsMiddleware());
448
+ }
449
+ const middleware = [];
450
+ if (options.middleware) middleware.push(...options.middleware, ...defaultMiddleware);
451
+ else middleware.push(...defaultMiddleware);
452
+ if (middleware.length === 0) return options.model;
453
+ return wrapLanguageModel({
454
+ model: options.model,
455
+ middleware
456
+ });
457
+ }
458
+ //#endregion
459
+ //#region src/utils/error.ts
460
+ /**
461
+ * Coerces an unknown thrown value into a proper `Error` instance.
462
+ *
463
+ * Handles the common cases where libraries throw non-`Error` values
464
+ * (e.g. plain API response bodies, arrays, Maps) that would otherwise
465
+ * serialize as `[object Object]` in error messages.
466
+ *
467
+ * @param thrown - The caught value from a `catch` block.
468
+ * @returns An `Error` with a meaningful `.message`. If `thrown` is
469
+ * already an `Error`, it is returned as-is. The original value is
470
+ * preserved as `.cause` for debugging.
471
+ *
472
+ * @example
473
+ * ```typescript
474
+ * try {
475
+ * await riskyCall()
476
+ * } catch (thrown) {
477
+ * const error = toError(thrown)
478
+ * console.error(error.message)
479
+ * }
480
+ * ```
481
+ */
482
+ function toError(thrown) {
483
+ if (isError(thrown)) return thrown;
484
+ if (isString(thrown)) return new Error(thrown, { cause: thrown });
485
+ return new Error(safeStringify(thrown), { cause: thrown });
486
+ }
487
+ /**
488
+ * Produce a human-readable string from any unknown value.
489
+ *
490
+ * Uses `JSON.stringify` for structured types (plain objects, arrays)
491
+ * so the message contains actual content instead of `[object Object]`.
492
+ * Maps and Sets are converted to their array representation first.
493
+ * Falls back to `String()` for primitives or when serialization fails
494
+ * (e.g. circular references).
495
+ *
496
+ * @param value - The value to stringify.
497
+ * @returns A meaningful string representation.
498
+ *
499
+ * @example
500
+ * ```typescript
501
+ * safeStringify({ status: 400 }) // '{"status":400}'
502
+ * safeStringify(new Map([['k', 'v']])) // '[["k","v"]]'
503
+ * safeStringify(null) // 'null'
504
+ * safeStringify(42) // '42'
505
+ * ```
506
+ */
507
+ function safeStringify(value) {
508
+ if (isNil(value) || isPrimitive(value)) return String(value);
509
+ return safeStringifyJSON(value) || String(value);
510
+ }
511
+ /**
512
+ * Safely serializes a value to a JSON string without throwing.
513
+ *
514
+ * Converts types that `JSON.stringify` handles poorly (Maps, Sets)
515
+ * into serializable equivalents before stringifying. Returns an empty
516
+ * string when serialization fails (e.g. circular references).
517
+ *
518
+ * @param value - The value to serialize.
519
+ * @returns The JSON string, or an empty string if serialization fails.
520
+ *
521
+ * @example
522
+ * ```typescript
523
+ * safeStringifyJSON({ status: 400 }) // '{"status":400}'
524
+ * safeStringifyJSON(new Map([['k', 'v']])) // '[["k","v"]]'
525
+ * safeStringifyJSON(circularObj) // ''
526
+ * ```
527
+ */
528
+ function safeStringifyJSON(value) {
529
+ const serializable = toSerializable(value);
530
+ const [error, json] = attempt(() => JSON.stringify(serializable));
531
+ if (!isNil(error) || isNil(json)) return "";
532
+ return json;
533
+ }
534
+ /**
535
+ * Convert types that `JSON.stringify` handles poorly into
536
+ * serializable equivalents.
537
+ *
538
+ * - `Map` → array of `[key, value]` entries
539
+ * - `Set` → array of values
540
+ * - Everything else → passed through unchanged
541
+ *
542
+ * @private
543
+ * @param value - The value to normalize.
544
+ * @returns A JSON-friendly representation.
545
+ */
546
+ function toSerializable(value) {
547
+ if (isMap(value)) return Array.from(value.entries());
548
+ if (isSet(value)) return Array.from(value);
549
+ return value;
550
+ }
551
+ //#endregion
552
+ //#region src/core/agents/base/agent.ts
553
+ /**
554
+ * Safely read a property from `overrides`, which may be undefined.
555
+ * Replaces `overrides?.prop` optional chaining.
556
+ *
557
+ * @private
558
+ */
559
+ function readOverride(overrides, key) {
560
+ if (overrides !== void 0) return overrides[key];
561
+ }
562
+ /**
563
+ * Safely compute the JSON-serialized length of a value.
564
+ * Returns 0 if serialization fails (e.g. circular refs, BigInt).
565
+ *
566
+ * @private
567
+ */
568
+ function safeSerializedLength(value) {
569
+ try {
570
+ const json = JSON.stringify(value);
571
+ return typeof json === "string" ? json.length : 0;
572
+ } catch {
573
+ return 0;
574
+ }
575
+ }
576
+ /**
577
+ * Return the value if the predicate is true, otherwise undefined.
578
+ * Replaces `predicate ? value : undefined` ternary.
579
+ *
580
+ * @private
581
+ */
582
+ function valueOrUndefined(predicate, value) {
583
+ if (predicate) return value;
584
+ }
585
+ /**
586
+ * Resolve an optional output param. Returns `resolveOutput(param)` if
587
+ * param is defined, otherwise undefined.
588
+ *
589
+ * @private
590
+ */
591
+ function resolveOptionalOutput(param) {
592
+ if (param !== void 0) return resolveOutput(param);
593
+ }
594
+ /**
595
+ * Safely extract a property from an object, returning `{}` if the
596
+ * property does not exist. Replaces `'key' in obj ? obj[key] : {}` ternary.
597
+ *
598
+ * @private
599
+ */
600
+ function extractProperty(obj, key) {
601
+ if (key in obj) return obj[key];
602
+ return {};
603
+ }
604
+ /**
605
+ * Extract token usage from a step's usage object, defaulting to 0
606
+ * when usage is undefined. Replaces optional chaining on `step.usage`.
607
+ *
608
+ * @private
609
+ */
610
+ function extractUsage(usage) {
611
+ if (usage !== void 0) {
612
+ const inputTokens = usage.inputTokens ?? 0;
613
+ const outputTokens = usage.outputTokens ?? 0;
614
+ return {
615
+ inputTokens,
616
+ outputTokens,
617
+ totalTokens: usage.totalTokens ?? inputTokens + outputTokens
618
+ };
619
+ }
620
+ return {
621
+ inputTokens: 0,
622
+ outputTokens: 0,
623
+ totalTokens: 0
624
+ };
625
+ }
626
+ /**
627
+ * Return `ifOutput` when `output` is defined, `ifText` otherwise.
628
+ * Replaces `output ? aiResult.output : aiResult.text` ternary.
629
+ *
630
+ * @private
631
+ */
632
+ function pickByOutput(output, ifOutput, ifText) {
633
+ if (output !== void 0) return ifOutput;
634
+ return ifText;
635
+ }
636
+ /**
637
+ * Create an agent with typed input, tools, subagents, and hooks.
638
+ *
639
+ * Agents run a tool loop (via the AI SDK's `generateText`) until a
640
+ * stop condition is met. They support:
641
+ * - **Typed input** via Zod schema + prompt template.
642
+ * - **Simple mode** — pass a string or messages directly.
643
+ * - **Tools** for function calling.
644
+ * - **Subagents** auto-wrapped as delegatable tools.
645
+ * - **Inline overrides** per call.
646
+ * - **Hooks** for observability.
647
+ * - **Result return type** that never throws.
648
+ *
649
+ * @typeParam TInput - Agent input type (default: `string | Message[]`).
650
+ * @typeParam TOutput - Agent output type (default: `string`).
651
+ * @typeParam TTools - Record of tools.
652
+ * @typeParam TSubAgents - Record of subagents.
653
+ * @param config - Agent configuration including name, model, schemas,
654
+ * tools, subagents, hooks, and logger.
655
+ * @returns An `Agent` instance with `.generate()`, `.stream()`, and `.fn()`.
656
+ *
657
+ * @example
658
+ * ```typescript
659
+ * // Simple mode — pass a string directly
660
+ * const helper = agent({
661
+ * name: 'helper',
662
+ * model: 'openai/gpt-4.1',
663
+ * system: 'You are a helpful assistant.',
664
+ * })
665
+ * await helper.generate('What is TypeScript?')
666
+ *
667
+ * // Typed mode — input schema + prompt template
668
+ * const summarizer = agent({
669
+ * name: 'summarizer',
670
+ * input: z.object({ text: z.string() }),
671
+ * model: 'openai/gpt-4.1',
672
+ * prompt: ({ input }) => `Summarize:\n\n${input.text}`,
673
+ * })
674
+ * await summarizer.generate({ text: '...' })
675
+ *
676
+ * // Export as a plain function
677
+ * export const summarize = summarizer.fn()
678
+ * ```
679
+ */
680
+ function agent(config) {
681
+ const baseLogger = config.logger ?? createDefaultLogger();
682
+ /**
683
+ * Validate raw input against the config schema, if present.
684
+ *
685
+ * Returns a discriminated union: `{ ok: true, input }` on success,
686
+ * `{ ok: false, error }` when validation fails.
687
+ *
688
+ * @private
689
+ */
690
+ function validateInput(rawInput) {
691
+ if (!config.input) return {
692
+ ok: true,
693
+ input: rawInput
694
+ };
695
+ const parsed = config.input.safeParse(rawInput);
696
+ if (!parsed.success) return {
697
+ ok: false,
698
+ error: {
699
+ code: "VALIDATION_ERROR",
700
+ message: `Input validation failed: ${parsed.error.message}`
701
+ }
702
+ };
703
+ return {
704
+ ok: true,
705
+ input: parsed.data
706
+ };
707
+ }
708
+ async function generate(rawInput, overrides) {
709
+ const validated = validateInput(rawInput);
710
+ if (!validated.ok) return {
711
+ ok: false,
712
+ error: validated.error
713
+ };
714
+ const input = validated.input;
715
+ const log = (readOverride(overrides, "logger") ?? baseLogger).child({ agentId: config.name });
716
+ const startedAt = Date.now();
717
+ try {
718
+ const model = await withModelMiddleware({ model: resolveModel(readOverride(overrides, "model") ?? config.model) });
719
+ const overrideTools = readOverride(overrides, "tools");
720
+ const overrideAgents = readOverride(overrides, "agents");
721
+ const mergedTools = {
722
+ ...config.tools,
723
+ ...overrideTools
724
+ };
725
+ const mergedAgents = {
726
+ ...config.agents,
727
+ ...overrideAgents
728
+ };
729
+ const hasTools = Object.keys(mergedTools).length > 0;
730
+ const hasAgents = Object.keys(mergedAgents).length > 0;
731
+ const aiTools = buildAITools(valueOrUndefined(hasTools, mergedTools), valueOrUndefined(hasAgents, mergedAgents));
732
+ const system = resolveSystem(readOverride(overrides, "system") ?? config.system, input);
733
+ const promptParams = buildPrompt(input, config);
734
+ const output = resolveOptionalOutput(readOverride(overrides, "output") ?? config.output);
735
+ await fireHooks(log, wrapHook(config.onStart, { input }), wrapHook(readOverride(overrides, "onStart"), { input }));
736
+ log.debug("agent.generate start", { name: config.name });
737
+ const maxSteps = readOverride(overrides, "maxSteps") ?? config.maxSteps ?? 20;
738
+ const overrideSignal = readOverride(overrides, "signal");
739
+ const stepCounter = { value: 0 };
740
+ const aiResult = await generateText({
741
+ model,
742
+ system,
743
+ ...promptParams,
744
+ tools: aiTools,
745
+ output,
746
+ stopWhen: stepCountIs(maxSteps),
747
+ abortSignal: overrideSignal,
748
+ onStepFinish: async (step) => {
749
+ const event = {
750
+ stepId: `${config.name}:${stepCounter.value++}`,
751
+ toolCalls: (step.toolCalls ?? []).map((tc) => {
752
+ const args = extractProperty(tc, "args");
753
+ return {
754
+ toolName: tc.toolName,
755
+ argsTextLength: safeSerializedLength(args)
756
+ };
757
+ }),
758
+ toolResults: (step.toolResults ?? []).map((tr) => {
759
+ const result = extractProperty(tr, "result");
760
+ return {
761
+ toolName: tr.toolName,
762
+ resultTextLength: safeSerializedLength(result)
763
+ };
764
+ }),
765
+ usage: extractUsage(step.usage)
766
+ };
767
+ await fireHooks(log, wrapHook(config.onStepFinish, event), wrapHook(readOverride(overrides, "onStepFinish"), event));
768
+ }
769
+ });
770
+ const duration = Date.now() - startedAt;
771
+ const generateResult = {
772
+ output: pickByOutput(output, aiResult.output, aiResult.text),
773
+ messages: aiResult.response.messages,
774
+ usage: toTokenUsage(aiResult.totalUsage),
775
+ finishReason: aiResult.finishReason
776
+ };
777
+ await fireHooks(log, wrapHook(config.onFinish, {
778
+ input,
779
+ result: generateResult,
780
+ duration
781
+ }), wrapHook(readOverride(overrides, "onFinish"), {
782
+ input,
783
+ result: generateResult,
784
+ duration
785
+ }));
786
+ log.debug("agent.generate finish", {
787
+ name: config.name,
788
+ duration
789
+ });
790
+ return {
791
+ ok: true,
792
+ ...generateResult
793
+ };
794
+ } catch (thrown) {
795
+ const error = toError(thrown);
796
+ const duration = Date.now() - startedAt;
797
+ log.error("agent.generate error", {
798
+ name: config.name,
799
+ error: error.message,
800
+ duration
801
+ });
802
+ await fireHooks(log, wrapHook(config.onError, {
803
+ input,
804
+ error
805
+ }), wrapHook(readOverride(overrides, "onError"), {
806
+ input,
807
+ error
808
+ }));
809
+ return {
810
+ ok: false,
811
+ error: {
812
+ code: "AGENT_ERROR",
813
+ message: error.message,
814
+ cause: error
815
+ }
816
+ };
817
+ }
818
+ }
819
+ async function stream(rawInput, overrides) {
820
+ const validated = validateInput(rawInput);
821
+ if (!validated.ok) return {
822
+ ok: false,
823
+ error: validated.error
824
+ };
825
+ const input = validated.input;
826
+ const log = (readOverride(overrides, "logger") ?? baseLogger).child({ agentId: config.name });
827
+ const startedAt = Date.now();
828
+ try {
829
+ const model = await withModelMiddleware({ model: resolveModel(readOverride(overrides, "model") ?? config.model) });
830
+ const overrideTools = readOverride(overrides, "tools");
831
+ const overrideAgents = readOverride(overrides, "agents");
832
+ const mergedTools = {
833
+ ...config.tools,
834
+ ...overrideTools
835
+ };
836
+ const mergedAgents = {
837
+ ...config.agents,
838
+ ...overrideAgents
839
+ };
840
+ const hasTools = Object.keys(mergedTools).length > 0;
841
+ const hasAgents = Object.keys(mergedAgents).length > 0;
842
+ const aiTools = buildAITools(valueOrUndefined(hasTools, mergedTools), valueOrUndefined(hasAgents, mergedAgents));
843
+ const system = resolveSystem(readOverride(overrides, "system") ?? config.system, input);
844
+ const promptParams = buildPrompt(input, config);
845
+ const output = resolveOptionalOutput(readOverride(overrides, "output") ?? config.output);
846
+ await fireHooks(log, wrapHook(config.onStart, { input }), wrapHook(readOverride(overrides, "onStart"), { input }));
847
+ log.debug("agent.stream start", { name: config.name });
848
+ const maxSteps = readOverride(overrides, "maxSteps") ?? config.maxSteps ?? 20;
849
+ const overrideSignal = readOverride(overrides, "signal");
850
+ const stepCounter = { value: 0 };
851
+ const aiResult = streamText({
852
+ model,
853
+ system,
854
+ ...promptParams,
855
+ tools: aiTools,
856
+ output,
857
+ stopWhen: stepCountIs(maxSteps),
858
+ abortSignal: overrideSignal,
859
+ onStepFinish: async (step) => {
860
+ const event = {
861
+ stepId: `${config.name}:${stepCounter.value++}`,
862
+ toolCalls: (step.toolCalls ?? []).map((tc) => {
863
+ const args = extractProperty(tc, "args");
864
+ return {
865
+ toolName: tc.toolName,
866
+ argsTextLength: safeSerializedLength(args)
867
+ };
868
+ }),
869
+ toolResults: (step.toolResults ?? []).map((tr) => {
870
+ const result = extractProperty(tr, "result");
871
+ return {
872
+ toolName: tr.toolName,
873
+ resultTextLength: safeSerializedLength(result)
874
+ };
875
+ }),
876
+ usage: extractUsage(step.usage)
877
+ };
878
+ await fireHooks(log, wrapHook(config.onStepFinish, event), wrapHook(readOverride(overrides, "onStepFinish"), event));
879
+ }
880
+ });
881
+ const { readable, writable } = new TransformStream();
882
+ const done = (async () => {
883
+ const writer = writable.getWriter();
884
+ try {
885
+ for await (const part of aiResult.fullStream) await writer.write(part);
886
+ await writer.close();
887
+ } catch (error) {
888
+ await writer.abort(error).catch(() => {});
889
+ throw error;
890
+ }
891
+ const finalOutput = pickByOutput(output, await aiResult.output, await aiResult.text);
892
+ const finalMessages = (await aiResult.response).messages;
893
+ const finalUsage = toTokenUsage(await aiResult.totalUsage);
894
+ const finalFinishReason = await aiResult.finishReason;
895
+ const duration = Date.now() - startedAt;
896
+ const generateResult = {
897
+ output: finalOutput,
898
+ messages: finalMessages,
899
+ usage: finalUsage,
900
+ finishReason: finalFinishReason
901
+ };
902
+ await fireHooks(log, wrapHook(config.onFinish, {
903
+ input,
904
+ result: generateResult,
905
+ duration
906
+ }), wrapHook(readOverride(overrides, "onFinish"), {
907
+ input,
908
+ result: generateResult,
909
+ duration
910
+ }));
911
+ log.debug("agent.stream finish", {
912
+ name: config.name,
913
+ duration
914
+ });
915
+ return {
916
+ output: finalOutput,
917
+ messages: finalMessages,
918
+ usage: finalUsage,
919
+ finishReason: finalFinishReason
920
+ };
921
+ })();
922
+ done.catch(async (thrown) => {
923
+ const error = toError(thrown);
924
+ const duration = Date.now() - startedAt;
925
+ log.error("agent.stream error", {
926
+ name: config.name,
927
+ error: error.message,
928
+ duration
929
+ });
930
+ await fireHooks(log, wrapHook(config.onError, {
931
+ input,
932
+ error
933
+ }), wrapHook(readOverride(overrides, "onError"), {
934
+ input,
935
+ error
936
+ }));
937
+ });
938
+ return {
939
+ ok: true,
940
+ output: done.then((r) => r.output),
941
+ messages: done.then((r) => r.messages),
942
+ usage: done.then((r) => r.usage),
943
+ finishReason: done.then((r) => r.finishReason),
944
+ fullStream: readable
945
+ };
946
+ } catch (thrown) {
947
+ const error = toError(thrown);
948
+ const duration = Date.now() - startedAt;
949
+ log.error("agent.stream error", {
950
+ name: config.name,
951
+ error: error.message,
952
+ duration
953
+ });
954
+ await fireHooks(log, wrapHook(config.onError, {
955
+ input,
956
+ error
957
+ }), wrapHook(readOverride(overrides, "onError"), {
958
+ input,
959
+ error
960
+ }));
961
+ return {
962
+ ok: false,
963
+ error: {
964
+ code: "AGENT_ERROR",
965
+ message: error.message,
966
+ cause: error
967
+ }
968
+ };
969
+ }
970
+ }
971
+ const agent = {
972
+ generate,
973
+ stream,
974
+ fn: () => generate
975
+ };
976
+ agent[RUNNABLE_META] = {
977
+ name: config.name,
978
+ inputSchema: config.input
979
+ };
980
+ return agent;
981
+ }
982
+ //#endregion
983
+ //#region src/core/agents/flow/messages.ts
984
+ /**
985
+ * Build the `toolCallId` for a step.
986
+ *
987
+ * Combines the step id with the global step index to produce a unique
988
+ * identifier that correlates tool-call and tool-result messages.
989
+ *
990
+ * @param stepId - The step's user-provided id.
991
+ * @param index - The step's global index within the flow.
992
+ * @returns A unique tool call identifier.
993
+ */
994
+ function buildToolCallId(stepId, index) {
995
+ return `${stepId}-${index}`;
996
+ }
997
+ /**
998
+ * Create an assistant message containing a synthetic tool-call part.
999
+ *
1000
+ * Emitted when a `$` step starts execution. The `input` field captures
1001
+ * the step's input snapshot (or `{}` when no input is available).
1002
+ *
1003
+ * @param toolCallId - Unique tool call identifier.
1004
+ * @param toolName - The step id used as the tool name.
1005
+ * @param args - The step's input snapshot.
1006
+ * @returns A `Message` with role `assistant` and a `tool-call` content part.
1007
+ */
1008
+ function createToolCallMessage(toolCallId, toolName, args) {
1009
+ return {
1010
+ role: "assistant",
1011
+ content: [{
1012
+ type: "tool-call",
1013
+ toolCallId,
1014
+ toolName,
1015
+ input: args ?? {}
1016
+ }]
1017
+ };
1018
+ }
1019
+ /**
1020
+ * Create a tool message containing a synthetic tool-result part.
1021
+ *
1022
+ * Emitted when a `$` step finishes execution. The `result` field captures
1023
+ * the step's output. When `isError` is true, the result represents an
1024
+ * error message.
1025
+ *
1026
+ * @param toolCallId - Unique tool call identifier (must match the paired tool-call).
1027
+ * @param toolName - The step id used as the tool name.
1028
+ * @param result - The step's output snapshot.
1029
+ * @param isError - Whether this result represents an error.
1030
+ * @returns A `Message` with role `tool` and a `tool-result` content part.
1031
+ */
1032
+ function createToolResultMessage(toolCallId, toolName, result, isError) {
1033
+ return {
1034
+ role: "tool",
1035
+ content: [{
1036
+ type: "tool-result",
1037
+ toolCallId,
1038
+ toolName,
1039
+ output: result ?? {},
1040
+ ...isError ? { isError: true } : {}
1041
+ }]
1042
+ };
1043
+ }
1044
+ /**
1045
+ * Safely serialize a value to a string for message content.
1046
+ *
1047
+ * Returns the value as-is when it's already a string. Otherwise
1048
+ * delegates to {@link safeStringify} which handles circular refs,
1049
+ * Maps, Sets, bigints, and other non-JSON-serializable types.
1050
+ *
1051
+ * @param value - The value to serialize.
1052
+ * @returns A string representation of the value.
1053
+ */
1054
+ function serializeMessageContent(value) {
1055
+ if (typeof value === "string") return value;
1056
+ return safeStringify(value ?? null);
1057
+ }
1058
+ /**
1059
+ * Create a user message from flow agent input.
1060
+ *
1061
+ * This is the first message in the flow's message array, representing
1062
+ * the input passed to `flowAgent.generate()` or `flowAgent.stream()`.
1063
+ *
1064
+ * @param input - The flow agent input.
1065
+ * @returns A `Message` with role `user`.
1066
+ */
1067
+ function createUserMessage(input) {
1068
+ return {
1069
+ role: "user",
1070
+ content: serializeMessageContent(input)
1071
+ };
1072
+ }
1073
+ /**
1074
+ * Create a final assistant message from flow agent output.
1075
+ *
1076
+ * This is the last message in the flow's message array, representing
1077
+ * the validated output returned by the handler.
1078
+ *
1079
+ * @param output - The flow agent output.
1080
+ * @returns A `Message` with role `assistant`.
1081
+ */
1082
+ function createAssistantMessage(output) {
1083
+ return {
1084
+ role: "assistant",
1085
+ content: serializeMessageContent(output)
1086
+ };
1087
+ }
1088
+ /**
1089
+ * Collect text content from assistant messages in the message array.
1090
+ *
1091
+ * Used for flow agents without an output schema — the output is
1092
+ * the concatenated text from sub-agent responses. Only considers
1093
+ * messages with string content (tool-call messages have array content
1094
+ * and are skipped).
1095
+ *
1096
+ * @param messages - The flow's message array.
1097
+ * @returns The concatenated assistant text, trimmed.
1098
+ */
1099
+ function collectTextFromMessages(messages) {
1100
+ return messages.filter((m) => m.role === "assistant" && typeof m.content === "string").map((m) => m.content).join("\n").trim();
1101
+ }
1102
+ //#endregion
1103
+ //#region src/core/agents/flow/steps/factory.ts
1104
+ /**
1105
+ * Create a `StepBuilder` (`$`) instance.
1106
+ *
1107
+ * The returned builder is the `$` object passed into every flow agent
1108
+ * handler and step callback. It owns the full step lifecycle:
1109
+ * trace registration, hook firing, error wrapping,
1110
+ * and `StepResult<T>` construction.
1111
+ *
1112
+ * @param options - Factory configuration with context, optional parent
1113
+ * hooks, and optional stream writer.
1114
+ * @returns A `StepBuilder` instance.
1115
+ */
1116
+ function createStepBuilder(options) {
1117
+ return createStepBuilderInternal(options, { current: 0 });
1118
+ }
1119
+ /**
1120
+ * Internal factory that accepts a shared index ref.
1121
+ *
1122
+ * Child builders (created for nested `$` in callbacks) share
1123
+ * the same ref so step indices are globally unique.
1124
+ */
1125
+ function createStepBuilderInternal(options, indexRef) {
1126
+ const { ctx, parentHooks, writer } = options;
1127
+ /**
1128
+ * Core step primitive — every other method delegates here.
1129
+ */
1130
+ async function step(config) {
1131
+ const onFinishHandler = buildOnFinishHandler(config.onFinish);
1132
+ return executeStep({
1133
+ id: config.id,
1134
+ type: "step",
1135
+ execute: config.execute,
1136
+ onStart: config.onStart,
1137
+ onFinish: onFinishHandler,
1138
+ onError: config.onError
1139
+ });
1140
+ }
1141
+ /**
1142
+ * Shared lifecycle for all step types.
1143
+ */
1144
+ async function executeStep(params) {
1145
+ const { id, type, execute, input, onStart, onFinish, onError } = params;
1146
+ const stepInfo = {
1147
+ id,
1148
+ index: indexRef.current++,
1149
+ type
1150
+ };
1151
+ const startedAt = Date.now();
1152
+ const childTrace = [];
1153
+ const child$ = createStepBuilderInternal({
1154
+ ctx: {
1155
+ signal: ctx.signal,
1156
+ log: ctx.log.child({ stepId: id }),
1157
+ trace: childTrace,
1158
+ messages: ctx.messages
1159
+ },
1160
+ parentHooks,
1161
+ writer
1162
+ }, indexRef);
1163
+ const toolCallId = buildToolCallId(id, stepInfo.index);
1164
+ ctx.messages.push(createToolCallMessage(toolCallId, id, input));
1165
+ if (writer != null) writer.write({
1166
+ type: "tool-call",
1167
+ toolCallId,
1168
+ toolName: id,
1169
+ input: input ?? {}
1170
+ }).catch((err) => {
1171
+ ctx.log.warn({
1172
+ err,
1173
+ toolCallId
1174
+ }, "failed to write tool-call event to stream");
1175
+ });
1176
+ const onStartHook = buildHookCallback(onStart, (fn) => fn({ id }));
1177
+ const parentOnStepStartHook = buildParentHookCallback(parentHooks, "onStepStart", (fn) => fn({ step: stepInfo }));
1178
+ await fireHooks(ctx.log, onStartHook, parentOnStepStartHook);
1179
+ try {
1180
+ const value = await execute({ $: child$ });
1181
+ const finishedAt = Date.now();
1182
+ const duration = finishedAt - startedAt;
1183
+ const usage = value != null && typeof value === "object" && "usage" in value ? value.usage : void 0;
1184
+ ctx.trace.push({
1185
+ id,
1186
+ type,
1187
+ input,
1188
+ startedAt,
1189
+ finishedAt,
1190
+ children: childTrace,
1191
+ output: value,
1192
+ ...usage != null ? { usage } : {}
1193
+ });
1194
+ ctx.messages.push(createToolResultMessage(toolCallId, id, value));
1195
+ if (writer != null) writer.write({
1196
+ type: "tool-result",
1197
+ toolCallId,
1198
+ toolName: id,
1199
+ input: input ?? {},
1200
+ output: value ?? null
1201
+ }).catch((err) => {
1202
+ ctx.log.warn({
1203
+ err,
1204
+ toolCallId
1205
+ }, "failed to write tool-result event to stream");
1206
+ });
1207
+ const onFinishHook = buildHookCallback(onFinish, (fn) => fn({
1208
+ id,
1209
+ result: value,
1210
+ duration
1211
+ }));
1212
+ const parentOnStepFinishHook = buildParentHookCallback(parentHooks, "onStepFinish", (fn) => fn({
1213
+ step: stepInfo,
1214
+ result: value,
1215
+ duration
1216
+ }));
1217
+ await fireHooks(ctx.log, onFinishHook, parentOnStepFinishHook);
1218
+ return {
1219
+ ok: true,
1220
+ value,
1221
+ step: stepInfo,
1222
+ duration
1223
+ };
1224
+ } catch (thrown) {
1225
+ const error = toError(thrown);
1226
+ const finishedAt = Date.now();
1227
+ const duration = finishedAt - startedAt;
1228
+ ctx.trace.push({
1229
+ id,
1230
+ type,
1231
+ input,
1232
+ startedAt,
1233
+ finishedAt,
1234
+ children: childTrace,
1235
+ error
1236
+ });
1237
+ const stepError = {
1238
+ code: "STEP_ERROR",
1239
+ message: error.message,
1240
+ cause: error,
1241
+ stepId: id
1242
+ };
1243
+ ctx.messages.push(createToolResultMessage(toolCallId, id, { error: error.message }, true));
1244
+ if (writer != null) writer.write({
1245
+ type: "tool-result",
1246
+ toolCallId,
1247
+ toolName: id,
1248
+ input: input ?? {},
1249
+ output: { error: error.message }
1250
+ }).catch((err) => {
1251
+ ctx.log.warn({
1252
+ err,
1253
+ toolCallId
1254
+ }, "failed to write error tool-result event to stream");
1255
+ });
1256
+ const onErrorHook = buildHookCallback(onError, (fn) => fn({
1257
+ id,
1258
+ error
1259
+ }));
1260
+ const parentOnStepFinishHook = buildParentHookCallback(parentHooks, "onStepFinish", (fn) => fn({
1261
+ step: stepInfo,
1262
+ result: void 0,
1263
+ duration
1264
+ }));
1265
+ await fireHooks(ctx.log, onErrorHook, parentOnStepFinishHook);
1266
+ return {
1267
+ ok: false,
1268
+ error: stepError,
1269
+ step: stepInfo,
1270
+ duration
1271
+ };
1272
+ }
1273
+ }
1274
+ async function agent(config) {
1275
+ const onFinishHandler = buildOnFinishHandlerWithCast(config.onFinish);
1276
+ return executeStep({
1277
+ id: config.id,
1278
+ type: "agent",
1279
+ input: config.input,
1280
+ execute: async () => {
1281
+ const agentConfig = {
1282
+ signal: ctx.signal,
1283
+ ...config.config,
1284
+ logger: ctx.log.child({ stepId: config.id })
1285
+ };
1286
+ if (config.stream && writer != null) {
1287
+ const streamResult = await config.agent.stream(config.input, agentConfig);
1288
+ if (!streamResult.ok) throw streamResult.error.cause ?? new Error(streamResult.error.message);
1289
+ const full = streamResult;
1290
+ for await (const part of full.fullStream) if (part.type === "text-delta") await writer.write(part);
1291
+ return {
1292
+ output: await full.output,
1293
+ messages: await full.messages,
1294
+ usage: await full.usage,
1295
+ finishReason: await full.finishReason
1296
+ };
1297
+ }
1298
+ const result = await config.agent.generate(config.input, agentConfig);
1299
+ if (!result.ok) throw result.error.cause ?? new Error(result.error.message);
1300
+ const full = result;
1301
+ return {
1302
+ output: full.output,
1303
+ messages: full.messages,
1304
+ usage: full.usage,
1305
+ finishReason: full.finishReason
1306
+ };
1307
+ },
1308
+ onStart: config.onStart,
1309
+ onFinish: onFinishHandler,
1310
+ onError: config.onError
1311
+ });
1312
+ }
1313
+ async function map(config) {
1314
+ const onFinishHandler = buildOnFinishHandlerWithCast(config.onFinish);
1315
+ return executeStep({
1316
+ id: config.id,
1317
+ type: "map",
1318
+ input: config.input,
1319
+ execute: async ({ $ }) => {
1320
+ const concurrency = config.concurrency;
1321
+ if (concurrency != null && concurrency !== Infinity) return poolMap(config.input, concurrency, ctx.signal, (item, index) => config.execute({
1322
+ item,
1323
+ index,
1324
+ $
1325
+ }));
1326
+ return Promise.all(config.input.map((item, index) => config.execute({
1327
+ item,
1328
+ index,
1329
+ $
1330
+ })));
1331
+ },
1332
+ onStart: config.onStart,
1333
+ onFinish: onFinishHandler,
1334
+ onError: config.onError
1335
+ });
1336
+ }
1337
+ async function each(config) {
1338
+ const onFinishHandler = buildOnFinishHandlerVoid(config.onFinish);
1339
+ return executeStep({
1340
+ id: config.id,
1341
+ type: "each",
1342
+ input: config.input,
1343
+ execute: async ({ $ }) => {
1344
+ for (const [i, item] of config.input.entries()) {
1345
+ if (ctx.signal.aborted) throw new Error("Aborted");
1346
+ await config.execute({
1347
+ item,
1348
+ index: i,
1349
+ $
1350
+ });
1351
+ }
1352
+ },
1353
+ onStart: config.onStart,
1354
+ onFinish: onFinishHandler,
1355
+ onError: config.onError
1356
+ });
1357
+ }
1358
+ async function reduce(config) {
1359
+ const onFinishHandler = buildOnFinishHandlerWithCast(config.onFinish);
1360
+ return executeStep({
1361
+ id: config.id,
1362
+ type: "reduce",
1363
+ input: config.input,
1364
+ execute: async ({ $ }) => {
1365
+ return await reduceSequential(config.input, config.initial, ctx.signal, (item, accumulator, index) => config.execute({
1366
+ item,
1367
+ accumulator,
1368
+ index,
1369
+ $
1370
+ }));
1371
+ },
1372
+ onStart: config.onStart,
1373
+ onFinish: onFinishHandler,
1374
+ onError: config.onError
1375
+ });
1376
+ }
1377
+ async function whileStep(config) {
1378
+ const onFinishHandler = buildOnFinishHandlerWithCast(config.onFinish);
1379
+ return executeStep({
1380
+ id: config.id,
1381
+ type: "while",
1382
+ execute: async ({ $ }) => {
1383
+ return await whileSequential(config.condition, ctx.signal, (index) => config.execute({
1384
+ index,
1385
+ $
1386
+ }));
1387
+ },
1388
+ onStart: config.onStart,
1389
+ onFinish: onFinishHandler,
1390
+ onError: config.onError
1391
+ });
1392
+ }
1393
+ async function all(config) {
1394
+ const onFinishHandler = buildOnFinishHandlerWithCast(config.onFinish);
1395
+ return executeStep({
1396
+ id: config.id,
1397
+ type: "all",
1398
+ execute: async ({ $ }) => {
1399
+ const ac = new AbortController();
1400
+ const onAbort = () => ac.abort();
1401
+ ctx.signal.addEventListener("abort", onAbort, { once: true });
1402
+ try {
1403
+ return await Promise.all(config.entries.map((factory) => factory(ac.signal, $)));
1404
+ } catch (err) {
1405
+ ac.abort();
1406
+ throw err;
1407
+ } finally {
1408
+ ctx.signal.removeEventListener("abort", onAbort);
1409
+ }
1410
+ },
1411
+ onStart: config.onStart,
1412
+ onFinish: onFinishHandler,
1413
+ onError: config.onError
1414
+ });
1415
+ }
1416
+ async function race(config) {
1417
+ const onFinishHandler = buildOnFinishHandlerRace(config.onFinish);
1418
+ return executeStep({
1419
+ id: config.id,
1420
+ type: "race",
1421
+ execute: async ({ $ }) => {
1422
+ const ac = new AbortController();
1423
+ const onAbort = () => ac.abort();
1424
+ ctx.signal.addEventListener("abort", onAbort, { once: true });
1425
+ try {
1426
+ return await Promise.race(config.entries.map((factory) => factory(ac.signal, $)));
1427
+ } finally {
1428
+ ac.abort();
1429
+ ctx.signal.removeEventListener("abort", onAbort);
1430
+ }
1431
+ },
1432
+ onStart: config.onStart,
1433
+ onFinish: onFinishHandler,
1434
+ onError: config.onError
1435
+ });
1436
+ }
1437
+ return {
1438
+ step,
1439
+ agent,
1440
+ map,
1441
+ each,
1442
+ reduce,
1443
+ while: whileStep,
1444
+ all,
1445
+ race
1446
+ };
1447
+ }
1448
+ function buildHookCallback(handler, invoke) {
1449
+ if (handler != null) return () => invoke(handler);
1450
+ }
1451
+ function buildParentHookCallback(hooks, key, invoke) {
1452
+ if (hooks == null) return;
1453
+ const fn = hooks[key];
1454
+ if (fn == null) return;
1455
+ return () => invoke(fn);
1456
+ }
1457
+ function buildOnFinishHandler(onFinish) {
1458
+ if (onFinish == null) return;
1459
+ return (event) => onFinish({
1460
+ id: event.id,
1461
+ result: event.result,
1462
+ duration: event.duration
1463
+ });
1464
+ }
1465
+ function buildOnFinishHandlerWithCast(onFinish) {
1466
+ if (onFinish == null) return;
1467
+ return (event) => onFinish({
1468
+ id: event.id,
1469
+ result: event.result,
1470
+ duration: event.duration
1471
+ });
1472
+ }
1473
+ function buildOnFinishHandlerVoid(onFinish) {
1474
+ if (onFinish == null) return;
1475
+ return (event) => onFinish({
1476
+ id: event.id,
1477
+ duration: event.duration
1478
+ });
1479
+ }
1480
+ function buildOnFinishHandlerRace(onFinish) {
1481
+ if (onFinish == null) return;
1482
+ return (event) => onFinish({
1483
+ id: event.id,
1484
+ result: event.result,
1485
+ duration: event.duration
1486
+ });
1487
+ }
1488
+ async function reduceSequential(items, initial, signal, fn) {
1489
+ async function loop(accumulator, index) {
1490
+ if (index >= items.length) return accumulator;
1491
+ if (signal.aborted) throw new Error("Aborted");
1492
+ return loop(await fn(items[index], accumulator, index), index + 1);
1493
+ }
1494
+ return loop(initial, 0);
1495
+ }
1496
+ async function whileSequential(condition, signal, fn) {
1497
+ async function loop(value, index) {
1498
+ if (!condition({
1499
+ value,
1500
+ index
1501
+ })) return value;
1502
+ if (signal.aborted) throw new Error("Aborted");
1503
+ return loop(await fn(index), index + 1);
1504
+ }
1505
+ return loop(void 0, 0);
1506
+ }
1507
+ /**
1508
+ * Worker-pool map with bounded concurrency.
1509
+ *
1510
+ * Runs `fn` over `items` with at most `concurrency` concurrent
1511
+ * executions. Results are returned in input order.
1512
+ */
1513
+ async function poolMap(items, concurrency, signal, fn) {
1514
+ const results = Array.from({ length: items.length });
1515
+ const indexRef = { current: 0 };
1516
+ async function worker() {
1517
+ while (indexRef.current < items.length) {
1518
+ if (signal.aborted) throw new Error("Aborted");
1519
+ const i = indexRef.current++;
1520
+ results[i] = await fn(items[i], i);
1521
+ }
1522
+ }
1523
+ const workers = Array.from({ length: Math.min(concurrency, items.length) }, () => worker());
1524
+ await Promise.all(workers);
1525
+ return results;
1526
+ }
1527
+ //#endregion
1528
+ //#region src/core/provider/usage.ts
1529
+ /**
1530
+ * Aggregate token counts across multiple raw tracking records.
1531
+ *
1532
+ * Sums each field, treating `undefined` as `0`.
1533
+ */
1534
+ function aggregateTokens(usages) {
1535
+ return {
1536
+ inputTokens: sumBy(usages, (u) => u.inputTokens ?? 0),
1537
+ outputTokens: sumBy(usages, (u) => u.outputTokens ?? 0),
1538
+ totalTokens: sumBy(usages, (u) => u.totalTokens ?? 0),
1539
+ cacheReadTokens: sumBy(usages, (u) => u.cacheReadTokens ?? 0),
1540
+ cacheWriteTokens: sumBy(usages, (u) => u.cacheWriteTokens ?? 0),
1541
+ reasoningTokens: sumBy(usages, (u) => u.reasoningTokens ?? 0)
1542
+ };
1543
+ }
1544
+ /**
1545
+ * Compute final usage for a single agent call.
1546
+ *
1547
+ * Aggregates token counts from one or more raw tracking records.
1548
+ * Returns a flat object with tokens + agentId.
1549
+ *
1550
+ * @param agentId - The agent that produced these records.
1551
+ * @param records - Raw tracking records from the agent's execution.
1552
+ * @returns Flat {@link AgentTokenUsage} with resolved token counts.
1553
+ */
1554
+ function agentUsage(agentId, records) {
1555
+ return {
1556
+ agentId,
1557
+ ...aggregateTokens(match(records).when(Array.isArray, (r) => r).otherwise((r) => [r]))
1558
+ };
1559
+ }
1560
+ /**
1561
+ * Compute final usage for a flow agent with multiple agent calls.
1562
+ *
1563
+ * Groups raw tracking records by `source.agentId`, computes per-agent
1564
+ * usage via {@link agentUsage}.
1565
+ *
1566
+ * @param records - Raw tracking records from all agents in the flow.
1567
+ * @returns {@link FlowAgentTokenUsage} with per-agent breakdown.
1568
+ */
1569
+ function flowAgentUsage(records) {
1570
+ const grouped = groupBy(records, (r) => match(r.source).with(P.nonNullable, (s) => match(s.agentId).with(P.string, (id) => id).otherwise(() => "unknown")).otherwise(() => "unknown"));
1571
+ return { usages: Object.entries(grouped).map(([id, group]) => agentUsage(id, group)) };
1572
+ }
1573
+ /**
1574
+ * Sum multiple {@link TokenUsage} objects field-by-field.
1575
+ *
1576
+ * Pure function — returns a new object without mutating any input.
1577
+ * Returns zero-valued usage when given an empty array.
1578
+ *
1579
+ * @param usages - Array of usage objects to sum.
1580
+ * @returns A new `TokenUsage` with each field summed.
1581
+ */
1582
+ function sumTokenUsage(usages) {
1583
+ return {
1584
+ inputTokens: sumBy(usages, (u) => u.inputTokens),
1585
+ outputTokens: sumBy(usages, (u) => u.outputTokens),
1586
+ totalTokens: sumBy(usages, (u) => u.totalTokens),
1587
+ cacheReadTokens: sumBy(usages, (u) => u.cacheReadTokens),
1588
+ cacheWriteTokens: sumBy(usages, (u) => u.cacheWriteTokens),
1589
+ reasoningTokens: sumBy(usages, (u) => u.reasoningTokens)
1590
+ };
1591
+ }
1592
+ //#endregion
1593
+ //#region src/lib/trace.ts
1594
+ /**
1595
+ * Recursively collect all {@link TokenUsage} values from a trace tree.
1596
+ *
1597
+ * Walks every entry (including nested children) and returns a flat
1598
+ * array of usage objects. Entries without usage are skipped.
1599
+ *
1600
+ * @param trace - The trace array to collect from.
1601
+ * @returns Flat array of {@link TokenUsage} values found in the tree.
1602
+ */
1603
+ function collectUsages(trace) {
1604
+ return trace.flatMap((entry) => {
1605
+ const usages = match(entry.usage).with(P.nonNullable, (u) => [u]).otherwise(() => []);
1606
+ if (entry.children != null && entry.children.length > 0) return [...usages, ...collectUsages(entry.children)];
1607
+ return usages;
1608
+ });
1609
+ }
1610
+ /**
1611
+ * Recursively deep-clone and freeze a trace array.
1612
+ *
1613
+ * Returns a structurally identical tree that is `Object.freeze`d at
1614
+ * every level, preventing post-run mutation of the result trace.
1615
+ *
1616
+ * @internal
1617
+ */
1618
+ function snapshotTrace(trace) {
1619
+ return Object.freeze(trace.map((entry) => {
1620
+ const childSpread = match(match(entry.children).with(P.nonNullable, (c) => snapshotTrace(c)).otherwise(() => void 0)).with(P.nonNullable, (c) => ({ children: c })).otherwise(() => ({}));
1621
+ return Object.freeze({
1622
+ ...entry,
1623
+ ...childSpread
1624
+ });
1625
+ }));
1626
+ }
1627
+ //#endregion
1628
+ //#region src/core/agents/flow/flow-agent.ts
1629
+ /**
1630
+ * Resolve the logger for a single flow agent execution.
1631
+ *
1632
+ * @private
1633
+ */
1634
+ function resolveFlowAgentLogger(base, flowAgentId, overrides) {
1635
+ return ((overrides && overrides.logger) ?? base).child({ flowAgentId });
1636
+ }
1637
+ /**
1638
+ * Augment the step builder with custom steps from the engine.
1639
+ *
1640
+ * @private
1641
+ */
1642
+ function augmentStepBuilder(base, ctx, internal) {
1643
+ if (internal && internal.augment$) return internal.augment$(base, ctx);
1644
+ return base;
1645
+ }
1646
+ function flowAgent(config, handler, _internal) {
1647
+ const baseLogger = config.logger ?? createDefaultLogger();
1648
+ /**
1649
+ * Resolve the handler output into a final value, validating against
1650
+ * the output schema when present. Also pushes the assistant message.
1651
+ *
1652
+ * Returns `{ ok: true, value }` on success, or `{ ok: false, message }`
1653
+ * when output validation fails.
1654
+ *
1655
+ * @private
1656
+ */
1657
+ function resolveFlowOutput(output, messages) {
1658
+ if (config.output !== void 0) {
1659
+ const outputParsed = config.output.safeParse(output);
1660
+ if (!outputParsed.success) return {
1661
+ ok: false,
1662
+ message: `Output validation failed: ${outputParsed.error.message}`
1663
+ };
1664
+ messages.push(createAssistantMessage(outputParsed.data));
1665
+ return {
1666
+ ok: true,
1667
+ value: outputParsed.data
1668
+ };
1669
+ }
1670
+ const text = collectTextFromMessages(messages);
1671
+ messages.push(createAssistantMessage(text));
1672
+ return {
1673
+ ok: true,
1674
+ value: text
1675
+ };
1676
+ }
1677
+ async function generate(input, overrides) {
1678
+ const inputParsed = config.input.safeParse(input);
1679
+ if (!inputParsed.success) return {
1680
+ ok: false,
1681
+ error: {
1682
+ code: "VALIDATION_ERROR",
1683
+ message: `Input validation failed: ${inputParsed.error.message}`
1684
+ }
1685
+ };
1686
+ const parsedInput = inputParsed.data;
1687
+ const startedAt = Date.now();
1688
+ const log = resolveFlowAgentLogger(baseLogger, config.name, overrides);
1689
+ const signal = overrides && overrides.signal || new AbortController().signal;
1690
+ const trace = [];
1691
+ const messages = [];
1692
+ const ctx = {
1693
+ signal,
1694
+ log,
1695
+ trace,
1696
+ messages
1697
+ };
1698
+ const $ = augmentStepBuilder(createStepBuilder({
1699
+ ctx,
1700
+ parentHooks: {
1701
+ onStepStart: config.onStepStart,
1702
+ onStepFinish: config.onStepFinish
1703
+ }
1704
+ }), ctx, _internal);
1705
+ messages.push(createUserMessage(parsedInput));
1706
+ await fireHooks(log, wrapHook(config.onStart, { input: parsedInput }), wrapHook(overrides && overrides.onStart, { input: parsedInput }));
1707
+ log.debug("flowAgent.generate start", { name: config.name });
1708
+ try {
1709
+ const outputResult = resolveFlowOutput(await handler({
1710
+ input: parsedInput,
1711
+ $,
1712
+ log
1713
+ }), messages);
1714
+ if (!outputResult.ok) return {
1715
+ ok: false,
1716
+ error: {
1717
+ code: "VALIDATION_ERROR",
1718
+ message: outputResult.message
1719
+ }
1720
+ };
1721
+ const resolvedOutput = outputResult.value;
1722
+ const duration = Date.now() - startedAt;
1723
+ const usage = sumTokenUsage(collectUsages(trace));
1724
+ const frozenTrace = snapshotTrace(trace);
1725
+ const result = {
1726
+ output: resolvedOutput,
1727
+ messages: [...messages],
1728
+ usage,
1729
+ finishReason: "stop",
1730
+ trace: frozenTrace,
1731
+ duration
1732
+ };
1733
+ await fireHooks(log, wrapHook(config.onFinish, {
1734
+ input: parsedInput,
1735
+ result,
1736
+ duration
1737
+ }), wrapHook(overrides && overrides.onFinish, {
1738
+ input: parsedInput,
1739
+ result,
1740
+ duration
1741
+ }));
1742
+ log.debug("flowAgent.generate finish", {
1743
+ name: config.name,
1744
+ duration
1745
+ });
1746
+ return {
1747
+ ok: true,
1748
+ ...result
1749
+ };
1750
+ } catch (thrown) {
1751
+ const error = toError(thrown);
1752
+ const duration = Date.now() - startedAt;
1753
+ log.error("flowAgent.generate error", {
1754
+ name: config.name,
1755
+ error: error.message,
1756
+ duration
1757
+ });
1758
+ await fireHooks(log, wrapHook(config.onError, {
1759
+ input: parsedInput,
1760
+ error
1761
+ }), wrapHook(overrides && overrides.onError, {
1762
+ input: parsedInput,
1763
+ error
1764
+ }));
1765
+ return {
1766
+ ok: false,
1767
+ error: {
1768
+ code: "FLOW_AGENT_ERROR",
1769
+ message: error.message,
1770
+ cause: error
1771
+ }
1772
+ };
1773
+ }
1774
+ }
1775
+ async function stream(input, overrides) {
1776
+ const inputParsed = config.input.safeParse(input);
1777
+ if (!inputParsed.success) return {
1778
+ ok: false,
1779
+ error: {
1780
+ code: "VALIDATION_ERROR",
1781
+ message: `Input validation failed: ${inputParsed.error.message}`
1782
+ }
1783
+ };
1784
+ const parsedInput = inputParsed.data;
1785
+ const startedAt = Date.now();
1786
+ const log = resolveFlowAgentLogger(baseLogger, config.name, overrides);
1787
+ const signal = overrides && overrides.signal || new AbortController().signal;
1788
+ const trace = [];
1789
+ const messages = [];
1790
+ const ctx = {
1791
+ signal,
1792
+ log,
1793
+ trace,
1794
+ messages
1795
+ };
1796
+ const { readable, writable } = new TransformStream();
1797
+ const writer = writable.getWriter();
1798
+ const $ = augmentStepBuilder(createStepBuilder({
1799
+ ctx,
1800
+ parentHooks: {
1801
+ onStepStart: config.onStepStart,
1802
+ onStepFinish: config.onStepFinish
1803
+ },
1804
+ writer
1805
+ }), ctx, _internal);
1806
+ messages.push(createUserMessage(parsedInput));
1807
+ await fireHooks(log, wrapHook(config.onStart, { input: parsedInput }), wrapHook(overrides && overrides.onStart, { input: parsedInput }));
1808
+ log.debug("flowAgent.stream start", { name: config.name });
1809
+ const done = (async () => {
1810
+ try {
1811
+ const outputResult = resolveFlowOutput(await handler({
1812
+ input: parsedInput,
1813
+ $,
1814
+ log
1815
+ }), messages);
1816
+ if (!outputResult.ok) throw new Error(outputResult.message);
1817
+ const resolvedOutput = outputResult.value;
1818
+ const duration = Date.now() - startedAt;
1819
+ const usage = sumTokenUsage(collectUsages(trace));
1820
+ const result = {
1821
+ output: resolvedOutput,
1822
+ messages: [...messages],
1823
+ usage,
1824
+ finishReason: "stop",
1825
+ trace: snapshotTrace(trace),
1826
+ duration
1827
+ };
1828
+ await fireHooks(log, wrapHook(config.onFinish, {
1829
+ input: parsedInput,
1830
+ result,
1831
+ duration
1832
+ }), wrapHook(overrides && overrides.onFinish, {
1833
+ input: parsedInput,
1834
+ result,
1835
+ duration
1836
+ }));
1837
+ log.debug("flowAgent.stream finish", {
1838
+ name: config.name,
1839
+ duration
1840
+ });
1841
+ await writer.write({
1842
+ type: "finish",
1843
+ finishReason: "stop",
1844
+ rawFinishReason: void 0,
1845
+ totalUsage: {
1846
+ inputTokens: usage.inputTokens,
1847
+ outputTokens: usage.outputTokens,
1848
+ totalTokens: usage.totalTokens
1849
+ }
1850
+ });
1851
+ await writer.close();
1852
+ return result;
1853
+ } catch (thrown) {
1854
+ const error = toError(thrown);
1855
+ const duration = Date.now() - startedAt;
1856
+ log.error("flowAgent.stream error", {
1857
+ name: config.name,
1858
+ error: error.message,
1859
+ duration
1860
+ });
1861
+ /* v8 ignore start -- defensive; writer rarely rejects in practice */
1862
+ await writer.write({
1863
+ type: "error",
1864
+ error
1865
+ }).catch((err) => {
1866
+ log.debug("failed to write error event to stream", { err });
1867
+ });
1868
+ await writer.close().catch((err) => {
1869
+ log.debug("failed to close stream writer", { err });
1870
+ });
1871
+ /* v8 ignore stop */
1872
+ await fireHooks(log, wrapHook(config.onError, {
1873
+ input: parsedInput,
1874
+ error
1875
+ }), wrapHook(overrides && overrides.onError, {
1876
+ input: parsedInput,
1877
+ error
1878
+ }));
1879
+ throw error;
1880
+ }
1881
+ })();
1882
+ done.catch(() => {});
1883
+ return {
1884
+ ok: true,
1885
+ output: done.then((r) => r.output),
1886
+ messages: done.then((r) => r.messages),
1887
+ usage: done.then((r) => r.usage),
1888
+ finishReason: done.then((r) => r.finishReason),
1889
+ fullStream: readable
1890
+ };
1891
+ }
1892
+ const agent = {
1893
+ generate,
1894
+ stream,
1895
+ fn: () => generate
1896
+ };
1897
+ agent[RUNNABLE_META] = {
1898
+ name: config.name,
1899
+ inputSchema: config.input
1900
+ };
1901
+ return agent;
1902
+ }
1903
+ //#endregion
1904
+ //#region src/core/agents/flow/engine.ts
1905
+ /**
1906
+ * Wrap a hook callback so it can be passed to `fireHooks`.
1907
+ *
1908
+ * Generic over `TEvent` so the hook is called with the correct
1909
+ * event type without resorting to `any`.
1910
+ */
1911
+ function createHookCaller(hook, event) {
1912
+ if (hook) return () => hook(event);
1913
+ }
1914
+ /**
1915
+ * Build a merged hook that runs engine and flow agent hooks sequentially.
1916
+ *
1917
+ * The `(event: never)` constraint is the widest function type under
1918
+ * strict mode — any single-argument function is assignable via
1919
+ * contravariance (`never extends T` for all `T`).
1920
+ */
1921
+ function buildMergedHook(log, engineHook, flowHook) {
1922
+ if (!engineHook && !flowHook) return;
1923
+ const merged = async (event) => {
1924
+ await fireHooks(log, createHookCaller(engineHook, event), createHookCaller(flowHook, event));
1925
+ };
1926
+ return merged;
1927
+ }
1928
+ /**
1929
+ * Built-in StepBuilder method names that custom steps must not shadow.
1930
+ *
1931
+ * @private
1932
+ */
1933
+ const RESERVED_STEP_NAMES = new Set([
1934
+ "step",
1935
+ "agent",
1936
+ "map",
1937
+ "each",
1938
+ "reduce",
1939
+ "while",
1940
+ "all",
1941
+ "race"
1942
+ ]);
1943
+ /**
1944
+ * Create a custom flow engine with additional step types
1945
+ * and/or default hooks.
1946
+ *
1947
+ * Returns a `flowAgent()`-like factory. Any custom steps defined
1948
+ * in `$` are merged into the handler's `$` parameter and fully typed.
1949
+ *
1950
+ * @typeParam TCustomSteps - The custom step definitions map.
1951
+ * @param config - Engine configuration including custom steps
1952
+ * and default hooks.
1953
+ * @returns A `FlowFactory` that creates flow agents with custom
1954
+ * `$` steps and engine-level default hooks.
1955
+ *
1956
+ * @example
1957
+ * ```typescript
1958
+ * const engine = createFlowEngine({
1959
+ * $: {
1960
+ * retry: async ({ ctx, config }) => {
1961
+ * let lastError: Error | undefined
1962
+ * for (let attempt = 0; attempt < config.attempts; attempt++) {
1963
+ * try {
1964
+ * return await config.execute({ attempt })
1965
+ * } catch (err) {
1966
+ * lastError = err as Error
1967
+ * }
1968
+ * }
1969
+ * throw lastError
1970
+ * },
1971
+ * },
1972
+ * onStart: ({ input }) => telemetry.trackStart(input),
1973
+ * })
1974
+ *
1975
+ * const myFlow = engine({
1976
+ * name: 'my-flow',
1977
+ * input: MyInput,
1978
+ * output: MyOutput,
1979
+ * }, async ({ input, $ }) => {
1980
+ * const data = await $.retry({
1981
+ * attempts: 3,
1982
+ * execute: async () => fetch('https://api.example.com/data'),
1983
+ * })
1984
+ * return data
1985
+ * })
1986
+ * ```
1987
+ */
1988
+ function createFlowEngine(engineConfig) {
1989
+ for (const name of Object.keys(engineConfig.$ ?? {})) if (RESERVED_STEP_NAMES.has(name)) throw new Error(`Custom step "${name}" conflicts with a built-in StepBuilder method`);
1990
+ return function engineCreateFlowAgent(flowConfig, handler) {
1991
+ const hookLog = (flowConfig.logger ?? createDefaultLogger()).child({ source: "engine" });
1992
+ const { onStart: engineOnStart } = engineConfig;
1993
+ const { onStart: flowOnStart } = flowConfig;
1994
+ const { onFinish: engineOnFinish } = engineConfig;
1995
+ const { onFinish: flowOnFinish } = flowConfig;
1996
+ const { onError: engineOnError } = engineConfig;
1997
+ const { onError: flowOnError } = flowConfig;
1998
+ const { onStepStart: engineOnStepStart } = engineConfig;
1999
+ const { onStepStart: flowOnStepStart } = flowConfig;
2000
+ const { onStepFinish: engineOnStepFinish } = engineConfig;
2001
+ const { onStepFinish: flowOnStepFinish } = flowConfig;
2002
+ const mergedConfig = {
2003
+ ...flowConfig,
2004
+ onStart: buildMergedHook(hookLog, engineOnStart, flowOnStart),
2005
+ onFinish: buildMergedHook(hookLog, engineOnFinish, flowOnFinish),
2006
+ onError: buildMergedHook(hookLog, engineOnError, flowOnError),
2007
+ onStepStart: buildMergedHook(hookLog, engineOnStepStart, flowOnStepStart),
2008
+ onStepFinish: buildMergedHook(hookLog, engineOnStepFinish, flowOnStepFinish)
2009
+ };
2010
+ const wrappedHandler = async (params) => {
2011
+ return handler({
2012
+ input: params.input,
2013
+ $: params.$,
2014
+ log: params.log
2015
+ });
2016
+ };
2017
+ return flowAgent(mergedConfig, wrappedHandler, { augment$: ($, ctx) => {
2018
+ const customSteps = {};
2019
+ for (const [name, factory] of Object.entries(engineConfig.$ ?? {})) customSteps[name] = (config) => factory({
2020
+ ctx: {
2021
+ signal: ctx.signal,
2022
+ log: ctx.log
2023
+ },
2024
+ config
2025
+ });
2026
+ return {
2027
+ ...$,
2028
+ ...customSteps
2029
+ };
2030
+ } });
2031
+ };
2032
+ }
2033
+ /**
2034
+ * Supported OpenRouter models with pricing data.
2035
+ */
2036
+ const MODELS = [...[
2037
+ {
2038
+ id: "openai/gpt-5.2-codex",
2039
+ category: "coding",
2040
+ pricing: {
2041
+ prompt: 175e-8,
2042
+ completion: 14e-6,
2043
+ inputCacheRead: 175e-9,
2044
+ webSearch: .01
2045
+ }
2046
+ },
2047
+ {
2048
+ id: "openai/gpt-5.2",
2049
+ category: "chat",
2050
+ pricing: {
2051
+ prompt: 175e-8,
2052
+ completion: 14e-6,
2053
+ inputCacheRead: 175e-9,
2054
+ webSearch: .01
2055
+ }
2056
+ },
2057
+ {
2058
+ id: "openai/gpt-5.1",
2059
+ category: "chat",
2060
+ pricing: {
2061
+ prompt: 125e-8,
2062
+ completion: 1e-5,
2063
+ inputCacheRead: 125e-9,
2064
+ webSearch: .01
2065
+ }
2066
+ },
2067
+ {
2068
+ id: "openai/gpt-5",
2069
+ category: "chat",
2070
+ pricing: {
2071
+ prompt: 125e-8,
2072
+ completion: 1e-5,
2073
+ inputCacheRead: 125e-9,
2074
+ webSearch: .01
2075
+ }
2076
+ },
2077
+ {
2078
+ id: "openai/gpt-5-mini",
2079
+ category: "chat",
2080
+ pricing: {
2081
+ prompt: 25e-8,
2082
+ completion: 2e-6,
2083
+ inputCacheRead: 25e-9,
2084
+ webSearch: .01
2085
+ }
2086
+ },
2087
+ {
2088
+ id: "openai/gpt-5-nano",
2089
+ category: "chat",
2090
+ pricing: {
2091
+ prompt: 5e-8,
2092
+ completion: 4e-7,
2093
+ inputCacheRead: 5e-9,
2094
+ webSearch: .01
2095
+ }
2096
+ },
2097
+ {
2098
+ id: "openai/gpt-4.1",
2099
+ category: "chat",
2100
+ pricing: {
2101
+ prompt: 2e-6,
2102
+ completion: 8e-6,
2103
+ inputCacheRead: 5e-7,
2104
+ webSearch: .01
2105
+ }
2106
+ },
2107
+ {
2108
+ id: "openai/gpt-4.1-mini",
2109
+ category: "chat",
2110
+ pricing: {
2111
+ prompt: 4e-7,
2112
+ completion: 16e-7,
2113
+ inputCacheRead: 1e-7,
2114
+ webSearch: .01
2115
+ }
2116
+ },
2117
+ {
2118
+ id: "openai/gpt-4.1-nano",
2119
+ category: "chat",
2120
+ pricing: {
2121
+ prompt: 1e-7,
2122
+ completion: 4e-7,
2123
+ inputCacheRead: 25e-9,
2124
+ webSearch: .01
2125
+ }
2126
+ },
2127
+ {
2128
+ id: "openai/gpt-4o",
2129
+ category: "chat",
2130
+ pricing: {
2131
+ prompt: 25e-7,
2132
+ completion: 1e-5,
2133
+ inputCacheRead: 125e-8
2134
+ }
2135
+ },
2136
+ {
2137
+ id: "openai/gpt-4o-mini",
2138
+ category: "chat",
2139
+ pricing: {
2140
+ prompt: 15e-8,
2141
+ completion: 6e-7,
2142
+ inputCacheRead: 75e-9
2143
+ }
2144
+ },
2145
+ {
2146
+ id: "openai/o3",
2147
+ category: "reasoning",
2148
+ pricing: {
2149
+ prompt: 2e-6,
2150
+ completion: 8e-6,
2151
+ inputCacheRead: 5e-7,
2152
+ webSearch: .01
2153
+ }
2154
+ },
2155
+ {
2156
+ id: "openai/o3-mini",
2157
+ category: "reasoning",
2158
+ pricing: {
2159
+ prompt: 11e-7,
2160
+ completion: 44e-7,
2161
+ inputCacheRead: 55e-8
2162
+ }
2163
+ },
2164
+ {
2165
+ id: "openai/o4-mini",
2166
+ category: "reasoning",
2167
+ pricing: {
2168
+ prompt: 11e-7,
2169
+ completion: 44e-7,
2170
+ inputCacheRead: 275e-9,
2171
+ webSearch: .01
2172
+ }
2173
+ }
2174
+ ]];
2175
+ /**
2176
+ * Look up a model definition by its identifier.
2177
+ *
2178
+ * @param id - The model identifier to look up.
2179
+ * @returns The matching model definition.
2180
+ * @throws {Error} If no model matches the given ID.
2181
+ *
2182
+ * @example
2183
+ * ```typescript
2184
+ * const m = model('openai/gpt-5.2-codex')
2185
+ * console.log(m.pricing.prompt) // 0.00000175
2186
+ * console.log(m.category) // 'coding'
2187
+ * ```
2188
+ */
2189
+ function model(id) {
2190
+ const found = MODELS.find((m) => m.id === id);
2191
+ if (!found) throw new Error(`Unknown model: ${id}`);
2192
+ return found;
2193
+ }
2194
+ /**
2195
+ * Look up a model definition by its identifier, returning `undefined` if not found.
2196
+ *
2197
+ * Unlike {@link model}, this does not throw for unknown IDs — useful when
2198
+ * the caller can gracefully handle missing pricing (e.g. non-OpenRouter models).
2199
+ *
2200
+ * @param id - The model identifier to look up.
2201
+ * @returns The matching model definition, or `undefined`.
2202
+ *
2203
+ * @example
2204
+ * ```typescript
2205
+ * const m = tryModel('anthropic/claude-sonnet-4-20250514')
2206
+ * if (m) {
2207
+ * console.log(m.pricing.prompt)
2208
+ * }
2209
+ * ```
2210
+ */
2211
+ function tryModel(id) {
2212
+ return MODELS.find((m) => m.id === id);
2213
+ }
2214
+ /**
2215
+ * Return supported model definitions, optionally filtered.
2216
+ *
2217
+ * @param filter - Optional predicate to filter models.
2218
+ * @returns A readonly array of matching model definitions.
2219
+ *
2220
+ * @example
2221
+ * ```typescript
2222
+ * const all = models()
2223
+ * const reasoning = models((m) => m.category === 'reasoning')
2224
+ * ```
2225
+ */
2226
+ function models(filter) {
2227
+ return match(filter).with(P.nullish, () => MODELS).otherwise((fn) => MODELS.filter(fn));
2228
+ }
2229
+ //#endregion
2230
+ //#region src/utils/result.ts
2231
+ /**
2232
+ * Create a success `Result`.
2233
+ *
2234
+ * Spreads the payload flat onto the object alongside `ok: true`.
2235
+ *
2236
+ * @param value - The success payload.
2237
+ * @returns A success `Result<T>`.
2238
+ *
2239
+ * @example
2240
+ * ```typescript
2241
+ * return ok({ output: 'hello', messages: [] })
2242
+ * // → { ok: true, output: 'hello', messages: [] }
2243
+ * ```
2244
+ */
2245
+ function ok(value) {
2246
+ return {
2247
+ ...value,
2248
+ ok: true
2249
+ };
2250
+ }
2251
+ /**
2252
+ * Create a failure `Result`.
2253
+ *
2254
+ * @param code - Machine-readable error code.
2255
+ * @param message - Human-readable error description.
2256
+ * @param cause - Optional original thrown error.
2257
+ * @returns A failure `Result` for any `T`.
2258
+ *
2259
+ * @example
2260
+ * ```typescript
2261
+ * return err('VALIDATION_ERROR', 'Name is required')
2262
+ * return err('AGENT_ERROR', error.message, error)
2263
+ * ```
2264
+ */
2265
+ function err(code, message, cause) {
2266
+ return {
2267
+ ok: false,
2268
+ error: {
2269
+ code,
2270
+ message,
2271
+ cause
2272
+ }
2273
+ };
2274
+ }
2275
+ /**
2276
+ * Narrow a `Result<T>` to its success branch.
2277
+ *
2278
+ * @param result - The result to check.
2279
+ * @returns `true` when `result.ok` is `true`.
2280
+ *
2281
+ * @example
2282
+ * ```typescript
2283
+ * const result = await agent.generate('hello')
2284
+ * if (isOk(result)) {
2285
+ * console.log(result.output)
2286
+ * }
2287
+ * ```
2288
+ */
2289
+ function isOk(result) {
2290
+ return result.ok;
2291
+ }
2292
+ /**
2293
+ * Narrow a `Result<T>` to its failure branch.
2294
+ *
2295
+ * @param result - The result to check.
2296
+ * @returns `true` when `result.ok` is `false`.
2297
+ *
2298
+ * @example
2299
+ * ```typescript
2300
+ * const result = await agent.generate('hello')
2301
+ * if (isErr(result)) {
2302
+ * console.error(result.error.code, result.error.message)
2303
+ * }
2304
+ * ```
2305
+ */
2306
+ function isErr(result) {
2307
+ return !result.ok;
2308
+ }
2309
+ //#endregion
2310
+ export { agent, agentUsage, collectUsages, createDefaultLogger, createFlowEngine, createOpenRouter, createStepBuilder, err, flowAgent, flowAgentUsage, isErr, isOk, model, models, ok, openrouter, resolveOutput, safeStringify, safeStringifyJSON, sumTokenUsage, toError, tool, tryModel };
2311
+
2312
+ //# sourceMappingURL=index.mjs.map