@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,95 @@
|
|
|
1
|
+
# Hooks
|
|
2
|
+
|
|
3
|
+
Hooks provide lifecycle callbacks for agents, workflows, and steps. All hooks are optional. Hook errors are swallowed (logged via `attemptEachAsync`, never thrown) so they never mask the original error or interrupt execution.
|
|
4
|
+
|
|
5
|
+
## Agent Hooks
|
|
6
|
+
|
|
7
|
+
Set on `AgentConfig`:
|
|
8
|
+
|
|
9
|
+
| Hook | Event fields | When |
|
|
10
|
+
| -------------- | ----------------------------- | ---------------------------------------------------------------------------- |
|
|
11
|
+
| `onStart` | `{ input }` | Before the model is called |
|
|
12
|
+
| `onFinish` | `{ input, result, duration }` | After successful generation |
|
|
13
|
+
| `onError` | `{ input, error }` | On error, before Result is returned |
|
|
14
|
+
| `onStepFinish` | `{ stepId }` | After each tool-loop step (counter-based: `agentName:0`, `agentName:1`, ...) |
|
|
15
|
+
|
|
16
|
+
## Workflow Hooks
|
|
17
|
+
|
|
18
|
+
Set on `WorkflowConfig`:
|
|
19
|
+
|
|
20
|
+
| Hook | Event fields | When |
|
|
21
|
+
| -------------- | -------------------------------------- | ----------------------------------------------------- |
|
|
22
|
+
| `onStart` | `{ input }` | After input validation, before handler runs |
|
|
23
|
+
| `onFinish` | `{ input, output, duration }` | After successful completion |
|
|
24
|
+
| `onError` | `{ input, error }` | On error, before Result is returned |
|
|
25
|
+
| `onStepStart` | `{ step: StepInfo }` | Before any `$` operation executes |
|
|
26
|
+
| `onStepFinish` | `{ step: StepInfo, result, duration }` | After any `$` operation completes (success AND error) |
|
|
27
|
+
|
|
28
|
+
`onStepFinish` fires on both success and error. On error, `result` is `undefined`.
|
|
29
|
+
|
|
30
|
+
## Step-Level Hooks
|
|
31
|
+
|
|
32
|
+
Each `$` config accepts its own hooks:
|
|
33
|
+
|
|
34
|
+
| Hook | Event fields | When |
|
|
35
|
+
| ---------- | -------------------------- | -------------------------- |
|
|
36
|
+
| `onStart` | `{ id }` | Before the step executes |
|
|
37
|
+
| `onFinish` | `{ id, result, duration }` | After successful execution |
|
|
38
|
+
| `onError` | `{ id, error }` | On error |
|
|
39
|
+
|
|
40
|
+
These are available on `$.step`, `$.agent`, `$.map`, `$.each`, `$.reduce`, `$.while`, `$.all`, and `$.race`.
|
|
41
|
+
|
|
42
|
+
## Per-Call Hooks
|
|
43
|
+
|
|
44
|
+
Agent per-call hooks are set on `AgentOverrides` (the second parameter to `.generate()` or `.stream()`). They have the same names as the base hooks but fire **after** the base hooks.
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
await myAgent.generate("hello", {
|
|
48
|
+
onStart: ({ input }) => console.log("call-level start"),
|
|
49
|
+
onFinish: ({ result, duration }) => console.log(`call done in ${duration}ms`),
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Hook Merging
|
|
54
|
+
|
|
55
|
+
Per-call hooks merge with base hooks -- base fires first, then call-level. Both are independently wrapped with `attemptEachAsync`, so an error in one hook does not prevent the other from running.
|
|
56
|
+
|
|
57
|
+
For workflow engines created with `createWorkflowEngine()`, engine-level hooks fire first, then workflow-level hooks fire second.
|
|
58
|
+
|
|
59
|
+
## Hook Execution Order
|
|
60
|
+
|
|
61
|
+
For a `$.agent` call inside a workflow:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
step.onStart -> workflow.onStepStart -> execute -> step.onFinish -> workflow.onStepFinish
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
On error, the sequence diverges:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
step.onStart -> workflow.onStepStart -> execute (throws) -> step.onError -> workflow.onStepFinish
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
For an agent's tool-loop steps:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
base.onStepFinish -> overrides.onStepFinish
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The `stepId` for agent tool-loop steps is counter-based: `agentName:0`, `agentName:1`, etc.
|
|
80
|
+
|
|
81
|
+
## Error Handling
|
|
82
|
+
|
|
83
|
+
All hooks are executed via `attemptEachAsync`, which:
|
|
84
|
+
|
|
85
|
+
1. Runs each hook sequentially.
|
|
86
|
+
2. Catches and swallows any errors -- hook failures never propagate.
|
|
87
|
+
3. Skips `undefined` hooks (no null checks needed at call sites).
|
|
88
|
+
|
|
89
|
+
This means a failing hook will never mask the original error or prevent other hooks from running.
|
|
90
|
+
|
|
91
|
+
## References
|
|
92
|
+
|
|
93
|
+
- [Agent](agent.md)
|
|
94
|
+
- [Workflow](workflow.md)
|
|
95
|
+
- [Step Builder ($)](step.md)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Core Concepts
|
|
2
|
+
|
|
3
|
+
The core module provides the fundamental building blocks: `agent()`, `workflow()`, `tool()`, and the `$` step builder. All public operations return `Result<T>` so callers never need try/catch.
|
|
4
|
+
|
|
5
|
+
## Result Type
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
type Result<T> = (T & { ok: true }) | { ok: false; error: ResultError };
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Success fields are **flat on the object** -- no `.value` wrapper. Callers pattern-match on `ok`:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const result = await myAgent.generate("hello");
|
|
15
|
+
|
|
16
|
+
if (!result.ok) {
|
|
17
|
+
console.error(result.error.code, result.error.message);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Success -- all fields from T are directly on result
|
|
22
|
+
console.log(result.output);
|
|
23
|
+
console.log(result.messages);
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`ResultError` has `code` (machine-readable), `message` (human-readable), and optional `cause` (original thrown error).
|
|
27
|
+
|
|
28
|
+
Helper constructors and type guards are exported: `ok()`, `err()`, `isOk()`, `isErr()`.
|
|
29
|
+
|
|
30
|
+
## Context
|
|
31
|
+
|
|
32
|
+
The framework creates an internal `Context` for each workflow execution. Users never create or pass this directly.
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
interface ExecutionContext {
|
|
36
|
+
readonly signal: AbortSignal;
|
|
37
|
+
readonly log: Logger;
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`ExecutionContext` is the public subset exposed to custom step factories via `createWorkflowEngine()`. The internal `Context` extends it with a mutable `trace: TraceEntry[]` array for recording the execution graph.
|
|
42
|
+
|
|
43
|
+
## Logger
|
|
44
|
+
|
|
45
|
+
Pino-compatible leveled logger with `child()` support for scoped bindings.
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
interface Logger {
|
|
49
|
+
debug(msg: string, meta?: Record<string, unknown>): void;
|
|
50
|
+
info(msg: string, meta?: Record<string, unknown>): void;
|
|
51
|
+
warn(msg: string, meta?: Record<string, unknown>): void;
|
|
52
|
+
error(msg: string, meta?: Record<string, unknown>): void;
|
|
53
|
+
child(bindings: Record<string, unknown>): Logger;
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Each level also supports pino's object-first overload: `log.info({ key: 'val' }, 'message')`.
|
|
58
|
+
|
|
59
|
+
The framework calls `child()` at scope boundaries (workflow, step, agent) so log output automatically includes execution context (`workflowId`, `stepId`, `agentId`). When no logger is injected, `createDefaultLogger()` provides a console-based fallback.
|
|
60
|
+
|
|
61
|
+
## TraceEntry
|
|
62
|
+
|
|
63
|
+
Every tracked `$` operation produces a `TraceEntry`. Nested operations appear as `children`, forming a tree that represents the full execution graph.
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
interface TraceEntry {
|
|
67
|
+
id: string; // from the $ config's `id` field
|
|
68
|
+
type: OperationType; // 'step' | 'agent' | 'map' | 'each' | 'reduce' | 'while' | 'all' | 'race'
|
|
69
|
+
input?: unknown; // captured when the operation starts
|
|
70
|
+
output?: unknown; // captured on success
|
|
71
|
+
startedAt: number; // Unix ms
|
|
72
|
+
finishedAt?: number; // Unix ms (undefined while running)
|
|
73
|
+
error?: Error; // set on failure
|
|
74
|
+
usage?: TokenUsage; // token usage (populated for successful agent steps)
|
|
75
|
+
children?: TraceEntry[];
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The trace is exposed on `WorkflowResult.trace` as a frozen (immutable) snapshot after workflow completion.
|
|
80
|
+
|
|
81
|
+
## References
|
|
82
|
+
|
|
83
|
+
- [Agent](agent.md)
|
|
84
|
+
- [Workflow](workflow.md)
|
|
85
|
+
- [Step Builder ($)](step.md)
|
|
86
|
+
- [Tools](tools.md)
|
|
87
|
+
- [Hooks](hooks.md)
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# $ StepBuilder
|
|
2
|
+
|
|
3
|
+
The `$` object is passed into every workflow handler and step callback. It provides tracked operations that register data flow in the execution trace. Every call through `$` becomes a `TraceEntry`.
|
|
4
|
+
|
|
5
|
+
`$` is passed into every callback, enabling composition and nesting. You can always skip `$` and use plain imperative code -- it just will not appear in the trace.
|
|
6
|
+
|
|
7
|
+
## StepResult
|
|
8
|
+
|
|
9
|
+
All `$` methods return `Promise<StepResult<T>>`:
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
type StepResult<T> =
|
|
13
|
+
| { ok: true; value: T; step: StepInfo; duration: number }
|
|
14
|
+
| { ok: false; error: StepError; step: StepInfo; duration: number };
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`StepInfo` identifies the step:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
interface StepInfo {
|
|
21
|
+
id: string; // from the $ config's `id` field
|
|
22
|
+
index: number; // auto-incrementing within the workflow
|
|
23
|
+
type: OperationType; // 'step' | 'agent' | 'map' | 'each' | 'reduce' | 'while' | 'all' | 'race'
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`StepError` extends `ResultError` with `stepId: string`.
|
|
28
|
+
|
|
29
|
+
## $.step
|
|
30
|
+
|
|
31
|
+
Single unit of work.
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
$.step<T>(config: StepConfig<T>): Promise<StepResult<T>>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
| Field | Required | Type | Description |
|
|
38
|
+
| ---------- | -------- | ------------------------------------------------------------ | ---------------------------- |
|
|
39
|
+
| `id` | Yes | `string` | Unique step identifier |
|
|
40
|
+
| `execute` | Yes | `(params: { $ }) => Promise<T>` | The step's logic |
|
|
41
|
+
| `onStart` | No | `(event: { id }) => void \| Promise<void>` | Hook: fires when step starts |
|
|
42
|
+
| `onFinish` | No | `(event: { id, result, duration }) => void \| Promise<void>` | Hook: fires on success |
|
|
43
|
+
| `onError` | No | `(event: { id, error }) => void \| Promise<void>` | Hook: fires on error |
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
const data = await $.step({
|
|
47
|
+
id: "fetch-data",
|
|
48
|
+
execute: async () => {
|
|
49
|
+
return await fetchData();
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (data.ok) {
|
|
54
|
+
console.log(data.value); // T
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## $.agent
|
|
59
|
+
|
|
60
|
+
Agent call as a tracked operation. Calls `agent.generate()` internally and unwraps the result -- agent errors become `StepError`, agent success becomes `StepResult<GenerateResult>`.
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
$.agent<TInput>(config: AgentStepConfig<TInput>): Promise<StepResult<GenerateResult>>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
| Field | Required | Type | Description |
|
|
67
|
+
| ---------- | -------- | ------------------ | ------------------------------------ |
|
|
68
|
+
| `id` | Yes | `string` | Unique step identifier |
|
|
69
|
+
| `agent` | Yes | `Runnable<TInput>` | The agent (or workflow) to invoke |
|
|
70
|
+
| `input` | Yes | `TInput` | Input to pass to the agent |
|
|
71
|
+
| `config` | No | `AgentOverrides` | Inline overrides for this agent call |
|
|
72
|
+
| `onStart` | No | hook | Hook: fires when step starts |
|
|
73
|
+
| `onFinish` | No | hook | Hook: fires on success |
|
|
74
|
+
| `onError` | No | hook | Hook: fires on error |
|
|
75
|
+
|
|
76
|
+
The framework automatically passes the abort signal and a scoped logger to the agent.
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
const result = await $.agent({
|
|
80
|
+
id: "analyze",
|
|
81
|
+
agent: analyzerAgent,
|
|
82
|
+
input: { files: ["src/main.ts"] },
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (result.ok) {
|
|
86
|
+
console.log(result.value.output); // the agent's output
|
|
87
|
+
console.log(result.value.messages); // full message history
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## $.map
|
|
92
|
+
|
|
93
|
+
Parallel map with optional concurrency limit. All items run concurrently (up to `concurrency` limit). Returns results in input order.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
$.map<T, R>(config: MapConfig<T, R>): Promise<StepResult<R[]>>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
| Field | Required | Type | Description |
|
|
100
|
+
| ------------- | -------- | -------------------------------------------- | ------------------------------------------- |
|
|
101
|
+
| `id` | Yes | `string` | Unique step identifier |
|
|
102
|
+
| `input` | Yes | `T[]` | Array of items to process |
|
|
103
|
+
| `execute` | Yes | `(params: { item, index, $ }) => Promise<R>` | Process a single item |
|
|
104
|
+
| `concurrency` | No | `number` | Max parallel executions (default: Infinity) |
|
|
105
|
+
| `onStart` | No | hook | Hook: fires when map starts |
|
|
106
|
+
| `onFinish` | No | hook | Hook: fires when all items complete |
|
|
107
|
+
| `onError` | No | hook | Hook: fires on error |
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
const results = await $.map({
|
|
111
|
+
id: "process-files",
|
|
112
|
+
input: files,
|
|
113
|
+
concurrency: 5,
|
|
114
|
+
execute: async ({ item, index }) => {
|
|
115
|
+
return await processFile(item);
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## $.each
|
|
121
|
+
|
|
122
|
+
Sequential side effects. Runs items one at a time in order. Returns `void`. Checks abort signal before each iteration.
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
$.each<T>(config: EachConfig<T>): Promise<StepResult<void>>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
| Field | Required | Type | Description |
|
|
129
|
+
| ---------- | -------- | ----------------------------------------------- | ----------------------------- |
|
|
130
|
+
| `id` | Yes | `string` | Unique step identifier |
|
|
131
|
+
| `input` | Yes | `T[]` | Array of items to process |
|
|
132
|
+
| `execute` | Yes | `(params: { item, index, $ }) => Promise<void>` | Process a single item |
|
|
133
|
+
| `onStart` | No | hook | Hook: fires when each starts |
|
|
134
|
+
| `onFinish` | No | hook | Hook: fires when all complete |
|
|
135
|
+
| `onError` | No | hook | Hook: fires on error |
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
await $.each({
|
|
139
|
+
id: "notify-users",
|
|
140
|
+
input: users,
|
|
141
|
+
execute: async ({ item }) => {
|
|
142
|
+
await sendNotification(item.email);
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## $.reduce
|
|
148
|
+
|
|
149
|
+
Sequential accumulation. Each step depends on the previous result. Checks abort signal before each iteration.
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
$.reduce<T, R>(config: ReduceConfig<T, R>): Promise<StepResult<R>>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
| Field | Required | Type | Description |
|
|
156
|
+
| ---------- | -------- | --------------------------------------------------------- | ------------------------------ |
|
|
157
|
+
| `id` | Yes | `string` | Unique step identifier |
|
|
158
|
+
| `input` | Yes | `T[]` | Array of items to reduce |
|
|
159
|
+
| `initial` | Yes | `R` | Initial accumulator value |
|
|
160
|
+
| `execute` | Yes | `(params: { item, accumulator, index, $ }) => Promise<R>` | Reduce function |
|
|
161
|
+
| `onStart` | No | hook | Hook: fires when reduce starts |
|
|
162
|
+
| `onFinish` | No | hook | Hook: fires when done |
|
|
163
|
+
| `onError` | No | hook | Hook: fires on error |
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
const total = await $.reduce({
|
|
167
|
+
id: "sum-scores",
|
|
168
|
+
input: items,
|
|
169
|
+
initial: 0,
|
|
170
|
+
execute: async ({ item, accumulator }) => {
|
|
171
|
+
return accumulator + item.score;
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## $.while
|
|
177
|
+
|
|
178
|
+
Conditional loop. Runs while a condition holds. Returns the last value, or `undefined` if the condition was false on first check. Checks abort signal before each iteration.
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
$.while<T>(config: WhileConfig<T>): Promise<StepResult<T | undefined>>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
| Field | Required | Type | Description |
|
|
185
|
+
| ----------- | -------- | --------------------------------------- | ---------------------------------------------- |
|
|
186
|
+
| `id` | Yes | `string` | Unique step identifier |
|
|
187
|
+
| `condition` | Yes | `(params: { value, index }) => boolean` | Loop condition (checked before each iteration) |
|
|
188
|
+
| `execute` | Yes | `(params: { index, $ }) => Promise<T>` | Execute one iteration |
|
|
189
|
+
| `onStart` | No | hook | Hook: fires when while starts |
|
|
190
|
+
| `onFinish` | No | hook | Hook: fires when loop ends |
|
|
191
|
+
| `onError` | No | hook | Hook: fires on error |
|
|
192
|
+
|
|
193
|
+
The `condition` receives the last iteration's value (or `undefined` before the first iteration) and the current iteration index.
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
const result = await $.while({
|
|
197
|
+
id: "poll-status",
|
|
198
|
+
condition: ({ value, index }) => index < 10 && value !== "complete",
|
|
199
|
+
execute: async ({ index }) => {
|
|
200
|
+
await sleep(1000);
|
|
201
|
+
return await checkStatus();
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## $.all
|
|
207
|
+
|
|
208
|
+
Concurrent heterogeneous operations -- like `Promise.all`. Entries are factory functions that receive an `AbortSignal` and return a promise. The framework creates an `AbortController`, links it to the parent signal, and starts all factories at the same time.
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
$.all(config: AllConfig): Promise<StepResult<unknown[]>>
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
| Field | Required | Type | Description |
|
|
215
|
+
| ---------- | -------- | ---------------- | ------------------------------------- |
|
|
216
|
+
| `id` | Yes | `string` | Unique step identifier |
|
|
217
|
+
| `entries` | Yes | `EntryFactory[]` | Factory functions to run concurrently |
|
|
218
|
+
| `onStart` | No | hook | Hook: fires when all starts |
|
|
219
|
+
| `onFinish` | No | hook | Hook: fires when all complete |
|
|
220
|
+
| `onError` | No | hook | Hook: fires on error |
|
|
221
|
+
|
|
222
|
+
Where `EntryFactory = (signal: AbortSignal) => Promise<any>`.
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
const [users, repos] = await $.all({
|
|
226
|
+
id: "fetch-data",
|
|
227
|
+
entries: [(signal) => fetchUsers(signal), (signal) => fetchRepos(signal)],
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## $.race
|
|
232
|
+
|
|
233
|
+
First-to-finish wins. Same `entries: EntryFactory[]` pattern as `$.all`. Losers are cancelled via abort signal when the winner resolves.
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
$.race(config: RaceConfig): Promise<StepResult<unknown>>
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
| Field | Required | Type | Description |
|
|
240
|
+
| ---------- | -------- | ---------------- | -------------------------------- |
|
|
241
|
+
| `id` | Yes | `string` | Unique step identifier |
|
|
242
|
+
| `entries` | Yes | `EntryFactory[]` | Factory functions to race |
|
|
243
|
+
| `onStart` | No | hook | Hook: fires when race starts |
|
|
244
|
+
| `onFinish` | No | hook | Hook: fires when winner resolves |
|
|
245
|
+
| `onError` | No | hook | Hook: fires on error |
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
const result = await $.race({
|
|
249
|
+
id: "fastest-provider",
|
|
250
|
+
entries: [(signal) => fetchFromProviderA(signal), (signal) => fetchFromProviderB(signal)],
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
if (result.ok) {
|
|
254
|
+
const fastest = result.value;
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Nesting
|
|
259
|
+
|
|
260
|
+
`$` is passed into every callback so you can nest operations freely. Nested operations appear as `children` in the parent's trace entry.
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
const result = await $.step({
|
|
264
|
+
id: "outer",
|
|
265
|
+
execute: async ({ $ }) => {
|
|
266
|
+
const inner = await $.step({
|
|
267
|
+
id: "inner",
|
|
268
|
+
execute: async () => "nested value",
|
|
269
|
+
});
|
|
270
|
+
return inner.ok ? inner.value : "fallback";
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## References
|
|
276
|
+
|
|
277
|
+
- [Workflow](workflow.md)
|
|
278
|
+
- [Hooks](hooks.md)
|
|
279
|
+
- [Core Overview](overview.md)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# tool()
|
|
2
|
+
|
|
3
|
+
`tool()` creates a tool for AI agent function calling. It wraps the AI SDK's `tool()` helper, converting Zod schemas to JSON Schema via `zodSchema()` for model I/O validation.
|
|
4
|
+
|
|
5
|
+
## Signature
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
function tool<TInput, TOutput>(config: ToolConfig<TInput, TOutput>): Tool<TInput, TOutput>;
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## ToolConfig
|
|
12
|
+
|
|
13
|
+
| Field | Required | Type | Description |
|
|
14
|
+
| --------------- | -------- | ------------------------------------- | ------------------------------------------ |
|
|
15
|
+
| `description` | Yes | `string` | What the tool does (shown to the model) |
|
|
16
|
+
| `title` | No | `string` | Display title for UIs and logs |
|
|
17
|
+
| `inputSchema` | Yes | `ZodType<TInput>` | Zod schema for validating and typing input |
|
|
18
|
+
| `outputSchema` | No | `ZodType<TOutput>` | Zod schema for validating output |
|
|
19
|
+
| `inputExamples` | No | `Array<{ input: TInput }>` | Example inputs to guide the model |
|
|
20
|
+
| `execute` | Yes | `(input: TInput) => Promise<TOutput>` | Execute the tool with validated input |
|
|
21
|
+
|
|
22
|
+
**Note:** There is no `name` field on `ToolConfig`. Tool names come from the object key when passed to an agent's `tools` record.
|
|
23
|
+
|
|
24
|
+
## Tool Type
|
|
25
|
+
|
|
26
|
+
The `Tool` type is the return type of the AI SDK's `tool()` function:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
type Tool<TInput = unknown, TOutput = unknown> = ReturnType<typeof aiTool<TInput, TOutput>>;
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Example
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { tool, agent } from "@joggr/agent-sdk";
|
|
36
|
+
import { z } from "zod";
|
|
37
|
+
|
|
38
|
+
const fetchPage = tool({
|
|
39
|
+
description: "Fetch the contents of a web page by URL",
|
|
40
|
+
inputSchema: z.object({
|
|
41
|
+
url: z.url(),
|
|
42
|
+
}),
|
|
43
|
+
execute: async ({ url }) => {
|
|
44
|
+
const res = await fetch(url);
|
|
45
|
+
return {
|
|
46
|
+
url,
|
|
47
|
+
status: res.status,
|
|
48
|
+
body: await res.text(),
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Tool name ("fetchPage") comes from the object key:
|
|
54
|
+
const assistant = agent({
|
|
55
|
+
name: "assistant",
|
|
56
|
+
model: "openai/gpt-4.1",
|
|
57
|
+
system: "You are a helpful assistant that can fetch web pages.",
|
|
58
|
+
tools: { fetchPage },
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Output validation
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
const calculator = tool({
|
|
66
|
+
description: "Evaluate a math expression",
|
|
67
|
+
inputSchema: z.object({ expression: z.string() }),
|
|
68
|
+
outputSchema: z.object({ result: z.number() }),
|
|
69
|
+
execute: async ({ expression }) => {
|
|
70
|
+
return { result: evaluate(expression) };
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Input examples
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
const searchTool = tool({
|
|
79
|
+
description: "Search the codebase for a pattern",
|
|
80
|
+
inputSchema: z.object({
|
|
81
|
+
query: z.string(),
|
|
82
|
+
fileType: z.string().optional(),
|
|
83
|
+
}),
|
|
84
|
+
inputExamples: [
|
|
85
|
+
{ input: { query: "function handleError", fileType: "ts" } },
|
|
86
|
+
{ input: { query: "TODO:" } },
|
|
87
|
+
],
|
|
88
|
+
execute: async ({ query, fileType }) => {
|
|
89
|
+
return await searchCodebase(query, fileType);
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## References
|
|
95
|
+
|
|
96
|
+
- [Agent](agent.md)
|
|
97
|
+
- [Core Overview](overview.md)
|
|
98
|
+
- [Guide: Create a Tool](../guides/create-tool.md)
|