@funkai/agents 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.generated/req.txt +1 -0
- package/.turbo/turbo-build.log +21 -0
- package/.turbo/turbo-test$colon$coverage.log +109 -0
- package/.turbo/turbo-test.log +141 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/CHANGELOG.md +16 -0
- package/ISSUES.md +540 -0
- package/LICENSE +21 -0
- package/README.md +128 -0
- package/banner.svg +97 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/core/agents/base/agent.ts.html +1705 -0
- package/coverage/lcov-report/core/agents/base/index.html +146 -0
- package/coverage/lcov-report/core/agents/base/output.ts.html +256 -0
- package/coverage/lcov-report/core/agents/base/utils.ts.html +694 -0
- package/coverage/lcov-report/core/agents/flow/engine.ts.html +928 -0
- package/coverage/lcov-report/core/agents/flow/flow-agent.ts.html +1462 -0
- package/coverage/lcov-report/core/agents/flow/index.html +146 -0
- package/coverage/lcov-report/core/agents/flow/messages.ts.html +508 -0
- package/coverage/lcov-report/core/agents/flow/steps/factory.ts.html +1975 -0
- package/coverage/lcov-report/core/agents/flow/steps/index.html +116 -0
- package/coverage/lcov-report/core/index.html +131 -0
- package/coverage/lcov-report/core/logger.ts.html +541 -0
- package/coverage/lcov-report/core/models/providers/index.html +116 -0
- package/coverage/lcov-report/core/models/providers/openai.ts.html +337 -0
- package/coverage/lcov-report/core/provider/index.html +131 -0
- package/coverage/lcov-report/core/provider/provider.ts.html +346 -0
- package/coverage/lcov-report/core/provider/usage.ts.html +376 -0
- package/coverage/lcov-report/core/tool.ts.html +577 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +221 -0
- package/coverage/lcov-report/lib/hooks.ts.html +262 -0
- package/coverage/lcov-report/lib/index.html +161 -0
- package/coverage/lcov-report/lib/middleware.ts.html +274 -0
- package/coverage/lcov-report/lib/runnable.ts.html +151 -0
- package/coverage/lcov-report/lib/trace.ts.html +520 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov-report/utils/attempt.ts.html +199 -0
- package/coverage/lcov-report/utils/error.ts.html +421 -0
- package/coverage/lcov-report/utils/index.html +176 -0
- package/coverage/lcov-report/utils/resolve.ts.html +208 -0
- package/coverage/lcov-report/utils/result.ts.html +538 -0
- package/coverage/lcov-report/utils/zod.ts.html +178 -0
- package/coverage/lcov.info +1566 -0
- package/dist/index.d.mts +2883 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2312 -0
- package/dist/index.mjs.map +1 -0
- package/docs/core/agent.md +231 -0
- package/docs/core/hooks.md +95 -0
- package/docs/core/overview.md +87 -0
- package/docs/core/step.md +279 -0
- package/docs/core/tools.md +98 -0
- package/docs/core/workflow.md +235 -0
- package/docs/guides/create-agent.md +224 -0
- package/docs/guides/create-tool.md +137 -0
- package/docs/guides/create-workflow.md +374 -0
- package/docs/overview.md +244 -0
- package/docs/provider/models.md +55 -0
- package/docs/provider/overview.md +106 -0
- package/docs/provider/usage.md +100 -0
- package/docs/research/experimental-context.md +167 -0
- package/docs/research/gap-analysis.md +86 -0
- package/docs/research/prepare-step-and-active-tools.md +138 -0
- package/docs/research/sub-agent-model.md +249 -0
- package/docs/troubleshooting.md +60 -0
- package/logo.svg +17 -0
- package/models.config.json +18 -0
- package/package.json +60 -0
- package/scripts/generate-models.ts +324 -0
- package/src/core/agents/base/agent.test.ts +1522 -0
- package/src/core/agents/base/agent.ts +547 -0
- package/src/core/agents/base/output.test.ts +93 -0
- package/src/core/agents/base/output.ts +57 -0
- package/src/core/agents/base/types.test-d.ts +69 -0
- package/src/core/agents/base/types.ts +503 -0
- package/src/core/agents/base/utils.test.ts +397 -0
- package/src/core/agents/base/utils.ts +197 -0
- package/src/core/agents/flow/engine.test.ts +452 -0
- package/src/core/agents/flow/engine.ts +281 -0
- package/src/core/agents/flow/flow-agent.test.ts +1027 -0
- package/src/core/agents/flow/flow-agent.ts +473 -0
- package/src/core/agents/flow/messages.test.ts +198 -0
- package/src/core/agents/flow/messages.ts +141 -0
- package/src/core/agents/flow/steps/agent.test.ts +280 -0
- package/src/core/agents/flow/steps/agent.ts +87 -0
- package/src/core/agents/flow/steps/all.test.ts +300 -0
- package/src/core/agents/flow/steps/all.ts +73 -0
- package/src/core/agents/flow/steps/builder.ts +124 -0
- package/src/core/agents/flow/steps/each.test.ts +257 -0
- package/src/core/agents/flow/steps/each.ts +61 -0
- package/src/core/agents/flow/steps/factory.test-d.ts +50 -0
- package/src/core/agents/flow/steps/factory.test.ts +1025 -0
- package/src/core/agents/flow/steps/factory.ts +645 -0
- package/src/core/agents/flow/steps/map.test.ts +273 -0
- package/src/core/agents/flow/steps/map.ts +75 -0
- package/src/core/agents/flow/steps/race.test.ts +290 -0
- package/src/core/agents/flow/steps/race.ts +59 -0
- package/src/core/agents/flow/steps/reduce.test.ts +310 -0
- package/src/core/agents/flow/steps/reduce.ts +73 -0
- package/src/core/agents/flow/steps/result.ts +27 -0
- package/src/core/agents/flow/steps/step.test.ts +402 -0
- package/src/core/agents/flow/steps/step.ts +51 -0
- package/src/core/agents/flow/steps/while.test.ts +283 -0
- package/src/core/agents/flow/steps/while.ts +75 -0
- package/src/core/agents/flow/types.ts +348 -0
- package/src/core/logger.test.ts +163 -0
- package/src/core/logger.ts +152 -0
- package/src/core/models/index.test.ts +137 -0
- package/src/core/models/index.ts +152 -0
- package/src/core/models/providers/openai.ts +84 -0
- package/src/core/provider/provider.test.ts +128 -0
- package/src/core/provider/provider.ts +99 -0
- package/src/core/provider/types.ts +98 -0
- package/src/core/provider/usage.test.ts +304 -0
- package/src/core/provider/usage.ts +97 -0
- package/src/core/tool.test.ts +65 -0
- package/src/core/tool.ts +164 -0
- package/src/core/types.ts +66 -0
- package/src/index.ts +95 -0
- package/src/lib/context.test.ts +86 -0
- package/src/lib/context.ts +49 -0
- package/src/lib/hooks.test.ts +102 -0
- package/src/lib/hooks.ts +59 -0
- package/src/lib/middleware.test.ts +122 -0
- package/src/lib/middleware.ts +63 -0
- package/src/lib/runnable.test.ts +41 -0
- package/src/lib/runnable.ts +22 -0
- package/src/lib/trace.test.ts +291 -0
- package/src/lib/trace.ts +145 -0
- package/src/models/index.ts +123 -0
- package/src/models/providers/index.ts +15 -0
- package/src/models/providers/openai.ts +84 -0
- package/src/testing/context.ts +32 -0
- package/src/testing/index.ts +2 -0
- package/src/testing/logger.ts +19 -0
- package/src/utils/attempt.test.ts +127 -0
- package/src/utils/attempt.ts +38 -0
- package/src/utils/error.test.ts +179 -0
- package/src/utils/error.ts +112 -0
- package/src/utils/resolve.test.ts +38 -0
- package/src/utils/resolve.ts +41 -0
- package/src/utils/result.test.ts +79 -0
- package/src/utils/result.ts +151 -0
- package/src/utils/zod.test.ts +69 -0
- package/src/utils/zod.ts +31 -0
- package/tsconfig.json +25 -0
- package/tsdown.config.ts +15 -0
- package/vitest.config.ts +46 -0
|
@@ -0,0 +1,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
|
+
});
|
package/src/core/tool.ts
ADDED
|
@@ -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
|
+
}
|