@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,97 @@
1
+ import { groupBy, sumBy } from "es-toolkit";
2
+ import { match, P } from "ts-pattern";
3
+
4
+ import type {
5
+ TokenUsage,
6
+ TokenUsageRecord,
7
+ AgentTokenUsage,
8
+ FlowAgentTokenUsage,
9
+ } from "@/core/provider/types.js";
10
+
11
+ /**
12
+ * Aggregate token counts across multiple raw tracking records.
13
+ *
14
+ * Sums each field, treating `undefined` as `0`.
15
+ */
16
+ function aggregateTokens(usages: TokenUsageRecord[]): TokenUsage {
17
+ return {
18
+ inputTokens: sumBy(usages, (u) => u.inputTokens ?? 0),
19
+ outputTokens: sumBy(usages, (u) => u.outputTokens ?? 0),
20
+ totalTokens: sumBy(usages, (u) => u.totalTokens ?? 0),
21
+ cacheReadTokens: sumBy(usages, (u) => u.cacheReadTokens ?? 0),
22
+ cacheWriteTokens: sumBy(usages, (u) => u.cacheWriteTokens ?? 0),
23
+ reasoningTokens: sumBy(usages, (u) => u.reasoningTokens ?? 0),
24
+ };
25
+ }
26
+
27
+ /**
28
+ * Compute final usage for a single agent call.
29
+ *
30
+ * Aggregates token counts from one or more raw tracking records.
31
+ * Returns a flat object with tokens + agentId.
32
+ *
33
+ * @param agentId - The agent that produced these records.
34
+ * @param records - Raw tracking records from the agent's execution.
35
+ * @returns Flat {@link AgentTokenUsage} with resolved token counts.
36
+ */
37
+ export function agentUsage(
38
+ agentId: string,
39
+ records: TokenUsageRecord | TokenUsageRecord[],
40
+ ): AgentTokenUsage {
41
+ const arr = match(records)
42
+ .when(Array.isArray, (r) => r)
43
+ .otherwise((r) => [r]);
44
+ const tokens = aggregateTokens(arr);
45
+
46
+ return {
47
+ agentId,
48
+ ...tokens,
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Compute final usage for a flow agent with multiple agent calls.
54
+ *
55
+ * Groups raw tracking records by `source.agentId`, computes per-agent
56
+ * usage via {@link agentUsage}.
57
+ *
58
+ * @param records - Raw tracking records from all agents in the flow.
59
+ * @returns {@link FlowAgentTokenUsage} with per-agent breakdown.
60
+ */
61
+ export function flowAgentUsage(records: TokenUsageRecord[]): FlowAgentTokenUsage {
62
+ const grouped = groupBy(records, (r) =>
63
+ match(r.source)
64
+ .with(P.nonNullable, (s) =>
65
+ match(s.agentId)
66
+ .with(P.string, (id) => id)
67
+ .otherwise(() => "unknown"),
68
+ )
69
+ .otherwise(() => "unknown"),
70
+ );
71
+
72
+ const usages = Object.entries(grouped).map(([id, group]) => agentUsage(id, group));
73
+
74
+ return {
75
+ usages,
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Sum multiple {@link TokenUsage} objects field-by-field.
81
+ *
82
+ * Pure function — returns a new object without mutating any input.
83
+ * Returns zero-valued usage when given an empty array.
84
+ *
85
+ * @param usages - Array of usage objects to sum.
86
+ * @returns A new `TokenUsage` with each field summed.
87
+ */
88
+ export function sumTokenUsage(usages: TokenUsage[]): TokenUsage {
89
+ return {
90
+ inputTokens: sumBy(usages, (u) => u.inputTokens),
91
+ outputTokens: sumBy(usages, (u) => u.outputTokens),
92
+ totalTokens: sumBy(usages, (u) => u.totalTokens),
93
+ cacheReadTokens: sumBy(usages, (u) => u.cacheReadTokens),
94
+ cacheWriteTokens: sumBy(usages, (u) => u.cacheWriteTokens),
95
+ reasoningTokens: sumBy(usages, (u) => u.reasoningTokens),
96
+ };
97
+ }
@@ -0,0 +1,65 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { z } from "zod";
3
+
4
+ import { tool } from "@/core/tool.js";
5
+
6
+ const greetTool = tool({
7
+ description: "Greet a person by name",
8
+ inputSchema: z.object({ name: z.string() }),
9
+ execute: async ({ name }) => ({
10
+ message: `Hello, ${name}!`,
11
+ }),
12
+ });
13
+
14
+ describe("tool", () => {
15
+ it("returns an object with description, inputSchema, and execute", () => {
16
+ expect(greetTool).toHaveProperty("description", "Greet a person by name");
17
+ expect(greetTool).toHaveProperty("inputSchema");
18
+ expect(greetTool).toHaveProperty("execute");
19
+ });
20
+
21
+ it("executes and returns the expected output", async () => {
22
+ if (greetTool.execute == null) {
23
+ throw new Error("Expected greetTool.execute to be defined");
24
+ }
25
+ const result = await greetTool.execute({ name: "Ada" }, { toolCallId: "test", messages: [] });
26
+ expect(result).toEqual({ message: "Hello, Ada!" });
27
+ });
28
+
29
+ it("wraps inputSchema as an AI SDK schema with jsonSchema", () => {
30
+ expect(greetTool.inputSchema).toHaveProperty("jsonSchema");
31
+ });
32
+
33
+ it("wraps outputSchema when provided", () => {
34
+ const t = tool({
35
+ description: "Tool with output schema",
36
+ inputSchema: z.object({ x: z.number() }),
37
+ outputSchema: z.object({ result: z.number() }),
38
+ execute: async ({ x }) => ({ result: x * 2 }),
39
+ });
40
+
41
+ expect(t).toHaveProperty("outputSchema");
42
+ });
43
+
44
+ it("sets title when provided", () => {
45
+ const t = tool({
46
+ description: "Tool with title",
47
+ title: "My Tool",
48
+ inputSchema: z.object({ x: z.number() }),
49
+ execute: async ({ x }) => x,
50
+ });
51
+
52
+ expect(t).toHaveProperty("title", "My Tool");
53
+ });
54
+
55
+ it("sets inputExamples when provided", () => {
56
+ const t = tool({
57
+ description: "Tool with examples",
58
+ inputSchema: z.object({ x: z.number() }),
59
+ inputExamples: [{ input: { x: 42 } }],
60
+ execute: async ({ x }) => x,
61
+ });
62
+
63
+ expect(t).toHaveProperty("inputExamples");
64
+ });
65
+ });
@@ -0,0 +1,164 @@
1
+ import { tool as aiTool, zodSchema } from "ai";
2
+ import { isFunction, isNil } from "es-toolkit";
3
+ import { has, isObject } from "es-toolkit/compat";
4
+ import { P, match } from "ts-pattern";
5
+ import type { ZodType } from "zod";
6
+
7
+ /**
8
+ * Configuration for creating a tool.
9
+ *
10
+ * @typeParam TInput - Input type, inferred from the `inputSchema` Zod schema.
11
+ * @typeParam TOutput - Output type, inferred from the `execute` return.
12
+ */
13
+ export interface ToolConfig<TInput, TOutput> {
14
+ /**
15
+ * Human-readable description of what the tool does.
16
+ *
17
+ * Shown to the model alongside the tool name. A good description
18
+ * helps the model decide when and how to call the tool.
19
+ */
20
+ description: string;
21
+
22
+ /**
23
+ * Display title for the tool.
24
+ *
25
+ * Optional human-readable title shown in UIs and logs.
26
+ */
27
+ title?: string;
28
+
29
+ /**
30
+ * Zod schema for validating and typing tool input.
31
+ *
32
+ * The schema is serialized to JSON Schema and sent to the model.
33
+ * Input from the model is validated against it before `execute`
34
+ * is called.
35
+ */
36
+ inputSchema: ZodType<TInput>;
37
+
38
+ /**
39
+ * Zod schema for validating tool output.
40
+ *
41
+ * When provided, the return value of `execute` is validated
42
+ * against this schema before being sent back to the model.
43
+ */
44
+ outputSchema?: ZodType<TOutput>;
45
+
46
+ /**
47
+ * Example inputs to guide the model.
48
+ *
49
+ * Helps the model understand expected input structure. Natively
50
+ * supported by Anthropic; for other providers, use
51
+ * `addToolInputExamplesMiddleware` to inject examples into the
52
+ * tool description.
53
+ */
54
+ inputExamples?: Array<{ input: TInput }>;
55
+
56
+ /**
57
+ * Execute the tool with validated input.
58
+ *
59
+ * Called by the framework after the model requests a tool call and
60
+ * the input passes schema validation.
61
+ *
62
+ * @param input - The validated tool input.
63
+ * @returns The tool output returned to the model.
64
+ */
65
+ execute: (input: TInput) => Promise<TOutput>;
66
+ }
67
+
68
+ /**
69
+ * A tool instance — the return type of `tool()` / `ai.tool()`.
70
+ *
71
+ * Defaults use `any` so `Record<string, Tool>` accepts concrete
72
+ * typed tools without contravariance issues (same pattern as
73
+ * {@link SubAgents}).
74
+ *
75
+ * @typeParam TInput - Tool input type.
76
+ * @typeParam TOutput - Tool output type.
77
+ */
78
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
+ export type Tool<TInput = any, TOutput = any> = ReturnType<typeof aiTool<TInput, TOutput>>;
80
+
81
+ /**
82
+ * Create a tool for AI agent function calling.
83
+ *
84
+ * Wraps the AI SDK's `tool()` helper. The `inputSchema` (and optional
85
+ * `outputSchema`) are wrapped via `zodSchema()` so the AI SDK can
86
+ * convert them to JSON Schema and validate model I/O at runtime.
87
+ *
88
+ * @see https://ai-sdk.dev/docs/reference/ai-sdk-core/tool
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * const fetchPage = tool({
93
+ * description: 'Fetch the contents of a web page by URL',
94
+ * inputSchema: z.object({
95
+ * url: z.url(),
96
+ * }),
97
+ * execute: async ({ url }) => {
98
+ * const res = await fetch(url)
99
+ * return {
100
+ * url,
101
+ * status: res.status,
102
+ * body: await res.text(),
103
+ * }
104
+ * },
105
+ * })
106
+ * ```
107
+ */
108
+ export function tool<TInput, TOutput>(config: ToolConfig<TInput, TOutput>): Tool<TInput, TOutput> {
109
+ const resolvedOutputSchema = resolveOutputSchema(config.outputSchema);
110
+ const result = {
111
+ description: config.description,
112
+ title: config.title,
113
+ inputSchema: zodSchema(config.inputSchema),
114
+ outputSchema: resolvedOutputSchema,
115
+ inputExamples: config.inputExamples,
116
+ execute: async (data: TInput) => config.execute(data),
117
+ };
118
+ assertTool<TInput, TOutput>(result);
119
+ return aiTool(result);
120
+ }
121
+
122
+ /**
123
+ * Resolve an optional Zod output schema into a zodSchema-wrapped value.
124
+ *
125
+ * @private
126
+ */
127
+ function resolveOutputSchema<TOutput>(
128
+ schema: ZodType<TOutput> | undefined,
129
+ ): ReturnType<typeof zodSchema> | undefined {
130
+ return match(schema)
131
+ .with(P.nullish, () => undefined)
132
+ .otherwise((value) => zodSchema(value));
133
+ }
134
+
135
+ /**
136
+ * Runtime assertion that narrows `value` to `Tool<TInput, TOutput>`.
137
+ *
138
+ * Validates structural shape at runtime — `inputSchema` is present and
139
+ * `execute` is a function. Generic type parameters (`TInput`, `TOutput`)
140
+ * are erased at runtime, so only the structural shape can be verified.
141
+ *
142
+ * This assertion exists because TypeScript cannot evaluate the AI SDK's
143
+ * `NeverOptional<TOutput>` conditional type when `TOutput` is an
144
+ * unresolved generic — a known limitation with higher-order conditional
145
+ * types. Using `asserts` is TypeScript's endorsed narrowing mechanism
146
+ * and provides a runtime safety net if the AI SDK's tool shape changes.
147
+ *
148
+ * @private
149
+ */
150
+ /* v8 ignore start -- defensive guard; tool() always constructs a valid object */
151
+ function assertTool<TInput, TOutput>(value: unknown): asserts value is Tool<TInput, TOutput> {
152
+ if (isNil(value) || !isObject(value)) {
153
+ throw new TypeError("Expected tool to be an object");
154
+ }
155
+
156
+ if (!has(value, "inputSchema")) {
157
+ throw new TypeError("Tool is missing required property: inputSchema");
158
+ }
159
+
160
+ if (!has(value, "execute") || !isFunction(value.execute)) {
161
+ throw new TypeError("Tool is missing required property: execute");
162
+ }
163
+ }
164
+ /* v8 ignore stop */
@@ -0,0 +1,66 @@
1
+ import type { AsyncIterableStream, LanguageModel } from "ai";
2
+
3
+ import type { StreamPart } from "@/core/agents/base/types.js";
4
+ import type { Result } from "@/utils/result.js";
5
+
6
+ /**
7
+ * A model reference.
8
+ *
9
+ * Accepts either:
10
+ * - A **string model ID** (e.g. `'openai/gpt-4.1'`) resolved via
11
+ * OpenRouter at runtime.
12
+ * - An **AI SDK `LanguageModel` instance** — including models wrapped
13
+ * with middleware via `wrapLanguageModel()`.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * // String ID — resolved via OpenRouter
18
+ * const agent1 = agent({
19
+ * name: 'my-agent',
20
+ * model: 'openai/gpt-4.1',
21
+ * system: 'You are helpful.',
22
+ * })
23
+ *
24
+ * // AI SDK provider instance
25
+ * import { openai } from '@ai-sdk/openai'
26
+ * const agent2 = agent({
27
+ * name: 'my-agent',
28
+ * model: openai('gpt-4.1'),
29
+ * system: 'You are helpful.',
30
+ * })
31
+ *
32
+ * // Middleware-wrapped model
33
+ * import { wrapLanguageModel, extractReasoningMiddleware } from 'ai'
34
+ * import { anthropic } from '@ai-sdk/anthropic'
35
+ * const agent3 = agent({
36
+ * name: 'reasoner',
37
+ * model: wrapLanguageModel({
38
+ * model: anthropic('claude-sonnet-4-5-20250929'),
39
+ * middleware: extractReasoningMiddleware({ tagName: 'think' }),
40
+ * }),
41
+ * system: 'Think step by step.',
42
+ * })
43
+ * ```
44
+ */
45
+ export type Model = string | LanguageModel;
46
+
47
+ /** @deprecated Use `Model` instead. */
48
+ export type ModelRef = Model;
49
+
50
+ /**
51
+ * A value that can be generated against — the shared contract
52
+ * between Agent and Workflow.
53
+ *
54
+ * Both `Agent` and `Workflow` satisfy this interface. Any API that
55
+ * accepts a `Runnable` works with either.
56
+ */
57
+ /* eslint-disable @typescript-eslint/no-explicit-any -- Runnable config accepts implementation-specific options that cannot be narrowed at the interface level */
58
+ export interface Runnable<TInput = unknown, TOutput = unknown> {
59
+ generate(input: TInput, config?: any): Promise<Result<{ output: TOutput }>>;
60
+ stream(
61
+ input: TInput,
62
+ config?: any,
63
+ ): Promise<Result<{ output: Promise<TOutput>; fullStream: AsyncIterableStream<StreamPart> }>>;
64
+ fn(): (input: TInput, config?: any) => Promise<Result<{ output: TOutput }>>;
65
+ }
66
+ /* eslint-enable @typescript-eslint/no-explicit-any */
package/src/index.ts ADDED
@@ -0,0 +1,95 @@
1
+ export { tool } from "@/core/tool.js";
2
+ export { agent } from "@/core/agents/base/agent.js";
3
+ export { resolveOutput } from "@/core/agents/base/output.js";
4
+ export { flowAgent } from "@/core/agents/flow/flow-agent.js";
5
+ export { createFlowEngine } from "@/core/agents/flow/engine.js";
6
+ export { createDefaultLogger } from "@/core/logger.js";
7
+ export { model, tryModel, models } from "@/core/models/index.js";
8
+ export { createOpenRouter, openrouter } from "@/core/provider/provider.js";
9
+ export { agentUsage, flowAgentUsage, sumTokenUsage } from "@/core/provider/usage.js";
10
+ export { createStepBuilder } from "@/core/agents/flow/steps/factory.js";
11
+
12
+ export type { Runnable, Model, ModelRef } from "@/core/types.js";
13
+ export type { TextStreamPart, AsyncIterableStream, ToolSet } from "ai";
14
+ export { toError, safeStringify, safeStringifyJSON } from "@/utils/error.js";
15
+ export { ok, err, isOk, isErr } from "@/utils/result.js";
16
+ export type { Result, ResultError } from "@/utils/result.js";
17
+ export type { Logger } from "@/core/logger.js";
18
+ export type { Tool, ToolConfig } from "@/core/tool.js";
19
+
20
+ export type { OutputSpec, OutputParam } from "@/core/agents/base/output.js";
21
+
22
+ export type {
23
+ SubAgents,
24
+ Message,
25
+ Agent,
26
+ AgentConfig,
27
+ AgentOverrides,
28
+ GenerateResult,
29
+ StreamResult,
30
+ StreamPart,
31
+ } from "@/core/agents/base/types.js";
32
+
33
+ export type {
34
+ FlowAgent,
35
+ FlowAgentConfig,
36
+ FlowAgentConfigBase,
37
+ FlowAgentConfigWithOutput,
38
+ FlowAgentConfigWithoutOutput,
39
+ FlowAgentOverrides,
40
+ FlowAgentHandler,
41
+ FlowAgentParams,
42
+ FlowAgentGenerateResult,
43
+ StepInfo,
44
+ } from "@/core/agents/flow/types.js";
45
+
46
+ export type {
47
+ FlowFactory,
48
+ FlowEngineConfig,
49
+ CustomStepDefinitions as FlowCustomStepDefinitions,
50
+ CustomStepFactory as FlowCustomStepFactory,
51
+ TypedCustomSteps as FlowTypedCustomSteps,
52
+ } from "@/core/agents/flow/engine.js";
53
+
54
+ export type { StepBuilderOptions } from "@/core/agents/flow/steps/factory.js";
55
+ export type { StepResult, StepError } from "@/core/agents/flow/steps/result.js";
56
+ export type { StepBuilder } from "@/core/agents/flow/steps/builder.js";
57
+ export type { StepConfig } from "@/core/agents/flow/steps/step.js";
58
+ export type { AgentStepConfig } from "@/core/agents/flow/steps/agent.js";
59
+ export type { MapConfig } from "@/core/agents/flow/steps/map.js";
60
+ export type { EachConfig } from "@/core/agents/flow/steps/each.js";
61
+ export type { ReduceConfig } from "@/core/agents/flow/steps/reduce.js";
62
+ export type { WhileConfig } from "@/core/agents/flow/steps/while.js";
63
+ export type { AllConfig, EntryFactory } from "@/core/agents/flow/steps/all.js";
64
+ export type { RaceConfig } from "@/core/agents/flow/steps/race.js";
65
+
66
+ export type {
67
+ OpenRouterLanguageModelId,
68
+ ModelId,
69
+ ModelCategory,
70
+ ModelPricing,
71
+ ModelDefinition,
72
+ } from "@/core/models/index.js";
73
+
74
+ export type {
75
+ LanguageModel,
76
+ TokenUsage,
77
+ TokenUsageRecord,
78
+ AgentTokenUsage,
79
+ FlowAgentTokenUsage,
80
+ } from "@/core/provider/types.js";
81
+
82
+ export type { Output } from "ai";
83
+
84
+ export type { ExecutionContext } from "@/lib/context.js";
85
+
86
+ /** @deprecated Use `ExecutionContext` instead. */
87
+ export type { Context } from "@/lib/context.js";
88
+
89
+ export { collectUsages } from "@/lib/trace.js";
90
+ export type { OperationType, TraceEntry } from "@/lib/trace.js";
91
+
92
+ /** @deprecated Use `OperationType` instead. */
93
+ export type { TraceType } from "@/lib/trace.js";
94
+
95
+ export type { ResolveParam } from "@/utils/resolve.js";
@@ -0,0 +1,86 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import type { TraceEntry } from "@/lib/trace.js";
4
+ import { createMockCtx, createMockExecutionCtx, createMockLogger } from "@/testing/index.js";
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // ExecutionContext via createMockExecutionCtx
8
+ // ---------------------------------------------------------------------------
9
+
10
+ describe("ExecutionContext", () => {
11
+ it("provides a non-aborted signal by default", () => {
12
+ const ctx = createMockExecutionCtx();
13
+ expect(ctx.signal.aborted).toBe(false);
14
+ });
15
+
16
+ it("provides a mock logger", () => {
17
+ const ctx = createMockExecutionCtx();
18
+ expect(ctx.log).toBeDefined();
19
+ expect(ctx.log.info).toBeDefined();
20
+ expect(ctx.log.debug).toBeDefined();
21
+ expect(ctx.log.warn).toBeDefined();
22
+ expect(ctx.log.error).toBeDefined();
23
+ expect(ctx.log.child).toBeDefined();
24
+ });
25
+
26
+ it("accepts signal override", () => {
27
+ const controller = new AbortController();
28
+ controller.abort();
29
+ const ctx = createMockExecutionCtx({ signal: controller.signal });
30
+ expect(ctx.signal.aborted).toBe(true);
31
+ });
32
+
33
+ it("accepts logger override", () => {
34
+ const log = createMockLogger();
35
+ const ctx = createMockExecutionCtx({ log });
36
+ expect(ctx.log).toBe(log);
37
+ });
38
+ });
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Context via createMockCtx
42
+ // ---------------------------------------------------------------------------
43
+
44
+ describe("Context", () => {
45
+ it("provides an empty trace by default", () => {
46
+ const ctx = createMockCtx();
47
+ expect(ctx.trace).toEqual([]);
48
+ });
49
+
50
+ it("provides a non-aborted signal by default", () => {
51
+ const ctx = createMockCtx();
52
+ expect(ctx.signal.aborted).toBe(false);
53
+ });
54
+
55
+ it("provides a mock logger", () => {
56
+ const ctx = createMockCtx();
57
+ expect(ctx.log).toBeDefined();
58
+ });
59
+
60
+ it("accepts trace override", () => {
61
+ const trace = [{ id: "step-1", type: "step" as const, startedAt: Date.now() }];
62
+ const ctx = createMockCtx({ trace });
63
+ expect(ctx.trace).toBe(trace);
64
+ expect(ctx.trace).toHaveLength(1);
65
+ });
66
+
67
+ it("accepts all overrides simultaneously", () => {
68
+ const controller = new AbortController();
69
+ const log = createMockLogger();
70
+ const trace = [{ id: "op-1", type: "agent" as const, startedAt: 1000 }];
71
+
72
+ const ctx = createMockCtx({ signal: controller.signal, log, trace });
73
+
74
+ expect(ctx.signal).toBe(controller.signal);
75
+ expect(ctx.log).toBe(log);
76
+ expect(ctx.trace).toBe(trace);
77
+ });
78
+
79
+ it("allows pushing entries to the mutable trace array", () => {
80
+ const ctx = createMockCtx();
81
+ ctx.trace.push({ id: "new-entry", type: "step", startedAt: Date.now() });
82
+ expect(ctx.trace).toHaveLength(1);
83
+ const entry = ctx.trace[0] as TraceEntry;
84
+ expect(entry.id).toBe("new-entry");
85
+ });
86
+ });
@@ -0,0 +1,49 @@
1
+ import type { Message } from "@/core/agents/base/types.js";
2
+ import type { Logger } from "@/core/logger.js";
3
+ import type { TraceEntry } from "@/lib/trace.js";
4
+
5
+ /**
6
+ * Public execution context for custom step factories.
7
+ *
8
+ * Provides the abort signal and scoped logger — the minimal surface
9
+ * needed to integrate with framework cancellation and logging.
10
+ * The mutable trace is internal-only.
11
+ */
12
+ export interface ExecutionContext {
13
+ readonly signal: AbortSignal;
14
+ readonly log: Logger;
15
+ }
16
+
17
+ /**
18
+ * Internal execution context.
19
+ *
20
+ * Created by the framework when a workflow or flow agent starts.
21
+ * Threaded through every `$` call automatically. Users never create,
22
+ * pass, or interact with this directly.
23
+ *
24
+ * @internal
25
+ * Only accessible to framework internals. Custom step factories
26
+ * receive {@link ExecutionContext} instead.
27
+ */
28
+ export interface Context extends ExecutionContext {
29
+ /**
30
+ * Execution trace — every tracked operation is recorded here.
31
+ *
32
+ * The framework appends entries as `$` operations start and complete.
33
+ * Read this after workflow/flow agent completion to inspect the full
34
+ * execution graph.
35
+ */
36
+ readonly trace: TraceEntry[];
37
+
38
+ /**
39
+ * Synthetic messages produced by `$` steps.
40
+ *
41
+ * Each tracked operation pushes a tool-call (assistant) message when
42
+ * it starts and a tool-result (tool) message when it finishes.
43
+ * The array is flat — nested steps push to the same array so messages
44
+ * appear in execution order.
45
+ *
46
+ * Used by `flowAgent` to populate `GenerateResult.messages`.
47
+ */
48
+ readonly messages: Message[];
49
+ }