@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,235 @@
|
|
|
1
|
+
# workflow()
|
|
2
|
+
|
|
3
|
+
`workflow()` creates a `Workflow` from a configuration object and an imperative handler function. The handler IS the workflow -- no step arrays, no definition objects. State is just variables. `$` is passed in for tracked operations.
|
|
4
|
+
|
|
5
|
+
## Signature
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
function workflow<TInput, TOutput>(
|
|
9
|
+
config: WorkflowConfig<TInput, TOutput>,
|
|
10
|
+
handler: WorkflowHandler<TInput, TOutput>,
|
|
11
|
+
): Workflow<TInput, TOutput>;
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## WorkflowConfig
|
|
15
|
+
|
|
16
|
+
| Field | Required | Type | Description |
|
|
17
|
+
| -------------- | -------- | --------------------------------------------------------------- | ------------------------------------------- |
|
|
18
|
+
| `name` | Yes | `string` | Unique workflow name (used in logs, traces) |
|
|
19
|
+
| `input` | Yes | `ZodType<TInput>` | Zod schema for validating input |
|
|
20
|
+
| `output` | Yes | `ZodType<TOutput>` | Zod schema for validating output |
|
|
21
|
+
| `logger` | No | `Logger` | Pino-compatible logger |
|
|
22
|
+
| `onStart` | No | `(event: { input }) => void \| Promise<void>` | Hook: fires when the workflow starts |
|
|
23
|
+
| `onFinish` | No | `(event: { input, output, duration }) => void \| Promise<void>` | Hook: fires on success |
|
|
24
|
+
| `onError` | No | `(event: { input, error }) => void \| Promise<void>` | Hook: fires on error |
|
|
25
|
+
| `onStepStart` | No | `(event: { step: StepInfo }) => void \| Promise<void>` | Hook: fires when any `$` step starts |
|
|
26
|
+
| `onStepFinish` | No | `(event: { step, result, duration }) => void \| Promise<void>` | Hook: fires when any `$` step finishes |
|
|
27
|
+
|
|
28
|
+
## WorkflowHandler
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
type WorkflowHandler<TInput, TOutput> = (params: WorkflowParams<TInput>) => Promise<TOutput>;
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The handler receives `{ input, $ }`:
|
|
35
|
+
|
|
36
|
+
- `input` -- the validated input after Zod parsing.
|
|
37
|
+
- `$` -- the `StepBuilder` for tracked operations (`$.step`, `$.agent`, `$.map`, etc.).
|
|
38
|
+
|
|
39
|
+
The handler returns `TOutput`, which is validated against the `output` Zod schema before being returned to the caller.
|
|
40
|
+
|
|
41
|
+
## Workflow Interface
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
interface Workflow<TInput, TOutput> {
|
|
45
|
+
generate(input: TInput, config?: WorkflowOverrides): Promise<Result<WorkflowResult<TOutput>>>;
|
|
46
|
+
stream(input: TInput, config?: WorkflowOverrides): Promise<Result<WorkflowStreamResult<TOutput>>>;
|
|
47
|
+
fn(): (input: TInput, config?: WorkflowOverrides) => Promise<Result<WorkflowResult<TOutput>>>;
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### generate()
|
|
52
|
+
|
|
53
|
+
Runs the workflow to completion. Returns `Result<WorkflowResult<TOutput>>`.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
interface WorkflowResult<TOutput> {
|
|
57
|
+
output: TOutput; // validated output
|
|
58
|
+
trace: readonly TraceEntry[]; // frozen execution trace tree
|
|
59
|
+
usage: TokenUsage; // aggregated token usage from all $.agent() calls
|
|
60
|
+
duration: number; // wall-clock time in ms
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
On success, `result.ok` is `true` and `output`, `trace`, `duration` are flat on the result object.
|
|
65
|
+
|
|
66
|
+
### stream()
|
|
67
|
+
|
|
68
|
+
Runs the workflow with streaming step progress. Returns `Result<WorkflowStreamResult<TOutput>>`.
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
interface WorkflowStreamResult<TOutput> {
|
|
72
|
+
output: TOutput; // available after stream completes
|
|
73
|
+
trace: readonly TraceEntry[]; // available after stream completes
|
|
74
|
+
usage: TokenUsage; // aggregated token usage (available after stream completes)
|
|
75
|
+
duration: number; // available after stream completes
|
|
76
|
+
stream: ReadableStream<StepEvent>;
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Subscribe to `stream` for real-time step progress events.
|
|
81
|
+
|
|
82
|
+
### StepEvent
|
|
83
|
+
|
|
84
|
+
Events emitted on the workflow stream:
|
|
85
|
+
|
|
86
|
+
| Type | Fields | Description |
|
|
87
|
+
| ----------------- | ---------------------------- | ------------------------- |
|
|
88
|
+
| `step:start` | `step: StepInfo` | A `$` operation started |
|
|
89
|
+
| `step:finish` | `step`, `result`, `duration` | A `$` operation completed |
|
|
90
|
+
| `step:error` | `step`, `error` | A `$` operation failed |
|
|
91
|
+
| `workflow:finish` | `output`, `duration` | The workflow completed |
|
|
92
|
+
|
|
93
|
+
### fn()
|
|
94
|
+
|
|
95
|
+
Returns a plain function with the same signature as `.generate()`. Use for clean single-function exports.
|
|
96
|
+
|
|
97
|
+
## WorkflowOverrides
|
|
98
|
+
|
|
99
|
+
Per-call overrides passed as the optional second parameter to `.generate()` or `.stream()`.
|
|
100
|
+
|
|
101
|
+
| Field | Type | Description |
|
|
102
|
+
| -------- | ------------- | ----------------------------- |
|
|
103
|
+
| `signal` | `AbortSignal` | Abort signal for cancellation |
|
|
104
|
+
|
|
105
|
+
When the signal fires, all in-flight `$` operations check `signal.aborted` and abort. The signal propagates through the entire execution tree.
|
|
106
|
+
|
|
107
|
+
## createWorkflowEngine()
|
|
108
|
+
|
|
109
|
+
For custom step types, use `createWorkflowEngine()`. It returns a `workflow()`-like factory with custom methods added to `$`.
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
function createWorkflowEngine<TCustomSteps>(
|
|
113
|
+
config: EngineConfig<TCustomSteps>,
|
|
114
|
+
): WorkflowFactory<TCustomSteps>;
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### EngineConfig
|
|
118
|
+
|
|
119
|
+
| Field | Type | Description |
|
|
120
|
+
| -------------- | ----------------------- | ------------------------------- |
|
|
121
|
+
| `$` | `CustomStepDefinitions` | Custom step types to add to `$` |
|
|
122
|
+
| `onStart` | hook | Default hook for all workflows |
|
|
123
|
+
| `onFinish` | hook | Default hook for all workflows |
|
|
124
|
+
| `onError` | hook | Default hook for all workflows |
|
|
125
|
+
| `onStepStart` | hook | Default hook for all workflows |
|
|
126
|
+
| `onStepFinish` | hook | Default hook for all workflows |
|
|
127
|
+
|
|
128
|
+
Engine-level hooks fire first, then workflow-level hooks fire second.
|
|
129
|
+
|
|
130
|
+
Each custom step factory receives `{ ctx: ExecutionContext, config }` where `ExecutionContext` provides the abort signal and scoped logger:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
type CustomStepFactory<TConfig, TResult> = (params: {
|
|
134
|
+
ctx: ExecutionContext;
|
|
135
|
+
config: TConfig;
|
|
136
|
+
}) => Promise<TResult>;
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Example
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
const engine = createWorkflowEngine({
|
|
143
|
+
$: {
|
|
144
|
+
retry: async ({ ctx, config }) => {
|
|
145
|
+
let lastError: Error | undefined;
|
|
146
|
+
for (let attempt = 0; attempt < config.attempts; attempt++) {
|
|
147
|
+
if (ctx.signal.aborted) throw new Error("Aborted");
|
|
148
|
+
try {
|
|
149
|
+
return await config.execute({ attempt });
|
|
150
|
+
} catch (err) {
|
|
151
|
+
lastError = err as Error;
|
|
152
|
+
await sleep(config.backoff * (attempt + 1));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
throw lastError;
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
onStart: ({ input }) => telemetry.trackStart(input),
|
|
159
|
+
onFinish: ({ output, duration }) => telemetry.trackFinish(output, duration),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const myWorkflow = engine(
|
|
163
|
+
{
|
|
164
|
+
name: "my-workflow",
|
|
165
|
+
input: MyInput,
|
|
166
|
+
output: MyOutput,
|
|
167
|
+
},
|
|
168
|
+
async ({ input, $ }) => {
|
|
169
|
+
// $.retry is fully typed from the engine config
|
|
170
|
+
const data = await $.retry({
|
|
171
|
+
attempts: 3,
|
|
172
|
+
backoff: 1000,
|
|
173
|
+
execute: async () => fetch("https://api.example.com/data"),
|
|
174
|
+
});
|
|
175
|
+
return data;
|
|
176
|
+
},
|
|
177
|
+
);
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Full Example
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
import { workflow, agent, tool } from "@joggr/agent-sdk";
|
|
184
|
+
import { z } from "zod";
|
|
185
|
+
|
|
186
|
+
const analyzeAgent = agent({
|
|
187
|
+
name: "analyzer",
|
|
188
|
+
model: "openai/gpt-4.1",
|
|
189
|
+
input: z.object({ files: z.array(z.string()) }),
|
|
190
|
+
prompt: ({ input }) => `Analyze these files:\n${input.files.join("\n")}`,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const InputSchema = z.object({ repo: z.string() });
|
|
194
|
+
const OutputSchema = z.object({ report: z.string(), fileCount: z.number() });
|
|
195
|
+
|
|
196
|
+
const reporter = workflow(
|
|
197
|
+
{
|
|
198
|
+
name: "reporter",
|
|
199
|
+
input: InputSchema,
|
|
200
|
+
output: OutputSchema,
|
|
201
|
+
},
|
|
202
|
+
async ({ input, $ }) => {
|
|
203
|
+
// $.step for a tracked unit of work
|
|
204
|
+
const files = await $.step({
|
|
205
|
+
id: "list-files",
|
|
206
|
+
execute: async () => listFiles(input.repo),
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
if (!files.ok) {
|
|
210
|
+
return { report: "Failed to list files", fileCount: 0 };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// $.agent for a tracked agent call
|
|
214
|
+
const analysis = await $.agent({
|
|
215
|
+
id: "analyze",
|
|
216
|
+
agent: analyzeAgent,
|
|
217
|
+
input: { files: files.value },
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
report: analysis.ok ? analysis.value.output : "Analysis failed",
|
|
222
|
+
fileCount: files.value.length,
|
|
223
|
+
};
|
|
224
|
+
},
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const result = await reporter.generate({ repo: "my-org/my-repo" });
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## References
|
|
231
|
+
|
|
232
|
+
- [Core Overview](overview.md)
|
|
233
|
+
- [Step Builder ($)](step.md)
|
|
234
|
+
- [Hooks](hooks.md)
|
|
235
|
+
- [Guide: Create a Workflow](../guides/create-workflow.md)
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Create an Agent
|
|
2
|
+
|
|
3
|
+
## Prerequisites
|
|
4
|
+
|
|
5
|
+
- `@pkg/agent-sdk` installed
|
|
6
|
+
- `OPENROUTER_API_KEY` environment variable set
|
|
7
|
+
- Familiarity with Zod schemas (for typed agents)
|
|
8
|
+
|
|
9
|
+
## Steps
|
|
10
|
+
|
|
11
|
+
### 1. Create a simple agent
|
|
12
|
+
|
|
13
|
+
Pass a `name`, `model`, and optional `system` prompt. In simple mode, `.generate()` accepts a raw `string` or `Message[]`.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { agent } from "@pkg/agent-sdk";
|
|
17
|
+
|
|
18
|
+
const helper = agent({
|
|
19
|
+
name: "helper",
|
|
20
|
+
model: "openai/gpt-4.1",
|
|
21
|
+
system: "You are a helpful assistant.",
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const result = await helper.generate("What is TypeScript?");
|
|
25
|
+
if (result.ok) {
|
|
26
|
+
console.log(result.output); // string
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 2. Create a typed agent
|
|
31
|
+
|
|
32
|
+
Add an `input` Zod schema and a `prompt` function. Both are required together. `.generate()` now accepts the typed input.
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { agent } from "@pkg/agent-sdk";
|
|
36
|
+
import { z } from "zod";
|
|
37
|
+
|
|
38
|
+
const summarizer = agent({
|
|
39
|
+
name: "summarizer",
|
|
40
|
+
model: "openai/gpt-4.1",
|
|
41
|
+
input: z.object({
|
|
42
|
+
text: z.string(),
|
|
43
|
+
maxLength: z.number().optional(),
|
|
44
|
+
}),
|
|
45
|
+
prompt: ({ input }) =>
|
|
46
|
+
`Summarize the following text${input.maxLength ? ` in under ${input.maxLength} words` : ""}:\n\n${input.text}`,
|
|
47
|
+
system: "You produce concise summaries.",
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const result = await summarizer.generate({
|
|
51
|
+
text: "A very long article...",
|
|
52
|
+
maxLength: 100,
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 3. Add tools
|
|
57
|
+
|
|
58
|
+
Pass a `tools` record. Tool names come from the object keys. See [Create a Tool](create-tool.md).
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import { agent, tool } from "@pkg/agent-sdk";
|
|
62
|
+
import { z } from "zod";
|
|
63
|
+
|
|
64
|
+
const fetchPage = tool({
|
|
65
|
+
description: "Fetch a web page by URL",
|
|
66
|
+
inputSchema: z.object({ url: z.url() }),
|
|
67
|
+
execute: async ({ url }) => {
|
|
68
|
+
const res = await fetch(url);
|
|
69
|
+
return { status: res.status, body: await res.text() };
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const researcher = agent({
|
|
74
|
+
name: "researcher",
|
|
75
|
+
model: "openai/gpt-4.1",
|
|
76
|
+
system: "You research topics by fetching web pages.",
|
|
77
|
+
tools: { fetchPage },
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 4. Add subagents
|
|
82
|
+
|
|
83
|
+
Pass an `agents` record. Each subagent is auto-wrapped as a delegatable tool. Abort signals propagate from parent to child.
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
const writer = agent({
|
|
87
|
+
name: "writer",
|
|
88
|
+
model: "openai/gpt-4.1",
|
|
89
|
+
input: z.object({ topic: z.string() }),
|
|
90
|
+
prompt: ({ input }) => `Write an article about ${input.topic}`,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const editor = agent({
|
|
94
|
+
name: "editor",
|
|
95
|
+
model: "openai/gpt-4.1",
|
|
96
|
+
system: "You review and improve articles. Delegate writing to the writer agent.",
|
|
97
|
+
agents: { writer },
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### 5. Use structured output
|
|
102
|
+
|
|
103
|
+
Pass an `output` config to get typed structured output instead of a string. Accepts AI SDK `Output` strategies or raw Zod schemas.
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { Output } from "ai";
|
|
107
|
+
|
|
108
|
+
// Zod schema auto-wrapped as Output.object()
|
|
109
|
+
const classifier = agent({
|
|
110
|
+
name: "classifier",
|
|
111
|
+
model: "openai/gpt-4.1",
|
|
112
|
+
output: z.object({
|
|
113
|
+
category: z.enum(["bug", "feature", "question"]),
|
|
114
|
+
confidence: z.number(),
|
|
115
|
+
}),
|
|
116
|
+
input: z.object({ title: z.string(), body: z.string() }),
|
|
117
|
+
prompt: ({ input }) => `Classify this issue:\n\nTitle: ${input.title}\nBody: ${input.body}`,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Or use Output directly
|
|
121
|
+
const tagger = agent({
|
|
122
|
+
name: "tagger",
|
|
123
|
+
model: "openai/gpt-4.1",
|
|
124
|
+
output: Output.array({ element: z.object({ tag: z.string(), score: z.number() }) }),
|
|
125
|
+
system: "Extract tags from the text.",
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 6. Stream output
|
|
130
|
+
|
|
131
|
+
Use `.stream()` for incremental text delivery. The result contains a `ReadableStream<string>` for live chunks, plus `output` and `messages` as promises that resolve after the stream completes.
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
const result = await helper.stream("Explain async/await in detail");
|
|
135
|
+
|
|
136
|
+
if (result.ok) {
|
|
137
|
+
// Consume text chunks as they arrive
|
|
138
|
+
const reader = result.stream.getReader();
|
|
139
|
+
while (true) {
|
|
140
|
+
const { done, value } = await reader.read();
|
|
141
|
+
if (done) break;
|
|
142
|
+
process.stdout.write(value);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Await final output and messages after stream completes
|
|
146
|
+
const finalOutput = await result.output;
|
|
147
|
+
const messages = await result.messages;
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### 7. Export as a plain function
|
|
152
|
+
|
|
153
|
+
Use `.fn()` for clean single-function exports.
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
export const summarize = summarizer.fn();
|
|
157
|
+
|
|
158
|
+
// Callers use it like a regular async function
|
|
159
|
+
const result = await summarize({ text: "...", maxLength: 50 });
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### 8. Handle cancellation
|
|
163
|
+
|
|
164
|
+
Pass an `AbortController` signal via per-call overrides.
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
const controller = new AbortController();
|
|
168
|
+
|
|
169
|
+
// Cancel after 10 seconds
|
|
170
|
+
setTimeout(() => controller.abort(), 10_000);
|
|
171
|
+
|
|
172
|
+
const result = await helper.generate("Explain quantum computing", {
|
|
173
|
+
signal: controller.signal,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
if (!result.ok) {
|
|
177
|
+
console.error(result.error.code); // 'AGENT_ERROR'
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### 9. Use per-call overrides
|
|
182
|
+
|
|
183
|
+
Override model, system prompt, tools, output, and hooks for a single call without changing the agent definition.
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
const result = await helper.generate("Explain monads", {
|
|
187
|
+
model: "anthropic/claude-sonnet-4-20250514",
|
|
188
|
+
system: "You explain concepts using simple analogies.",
|
|
189
|
+
maxSteps: 5,
|
|
190
|
+
onStart: ({ input }) => console.log("Starting with:", input),
|
|
191
|
+
onFinish: ({ result, duration }) => console.log(`Done in ${duration}ms`),
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Verification
|
|
196
|
+
|
|
197
|
+
- `result.ok` is `true` on success
|
|
198
|
+
- `result.output` contains the agent's response (string or typed object)
|
|
199
|
+
- `result.messages` contains the full message history including tool calls
|
|
200
|
+
|
|
201
|
+
## Troubleshooting
|
|
202
|
+
|
|
203
|
+
### OPENROUTER_API_KEY not set
|
|
204
|
+
|
|
205
|
+
**Fix:** Set the `OPENROUTER_API_KEY` environment variable in your `.env` file or shell environment.
|
|
206
|
+
|
|
207
|
+
### Agent has `input` schema but no `prompt` function
|
|
208
|
+
|
|
209
|
+
**Fix:** Provide both `input` and `prompt`, or omit both for simple mode.
|
|
210
|
+
|
|
211
|
+
### Agent has `prompt` function but no `input` schema
|
|
212
|
+
|
|
213
|
+
**Fix:** Provide both `input` and `prompt`, or omit both for simple mode.
|
|
214
|
+
|
|
215
|
+
### Input validation failed
|
|
216
|
+
|
|
217
|
+
**Fix:** Check that the input matches the Zod schema. Ensure all required fields are present and types are correct.
|
|
218
|
+
|
|
219
|
+
## References
|
|
220
|
+
|
|
221
|
+
- [Create a Tool](create-tool.md)
|
|
222
|
+
- [Create a Workflow](create-workflow.md)
|
|
223
|
+
- [Provider Overview](../provider/overview.md)
|
|
224
|
+
- [Troubleshooting](../troubleshooting.md)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Create a Tool
|
|
2
|
+
|
|
3
|
+
## Prerequisites
|
|
4
|
+
|
|
5
|
+
- `@pkg/agent-sdk` installed
|
|
6
|
+
- Familiarity with Zod schemas
|
|
7
|
+
|
|
8
|
+
## Steps
|
|
9
|
+
|
|
10
|
+
### 1. Define a tool with `tool()`
|
|
11
|
+
|
|
12
|
+
A tool wraps a function for AI agent function calling. Provide a `description`, an `inputSchema`, and an `execute` function.
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { tool } from "@pkg/agent-sdk";
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
|
|
18
|
+
const fetchPage = tool({
|
|
19
|
+
description: "Fetch a web page by URL",
|
|
20
|
+
inputSchema: z.object({ url: z.url() }),
|
|
21
|
+
execute: async (input) => {
|
|
22
|
+
const res = await fetch(input.url);
|
|
23
|
+
return { url: input.url, status: res.status, body: await res.text() };
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The `execute` function receives the validated input directly -- not wrapped in an object.
|
|
29
|
+
|
|
30
|
+
### 2. Register the tool on an agent
|
|
31
|
+
|
|
32
|
+
Pass tools as a record on the agent config. The tool's name comes from the object key, not from the tool definition itself.
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { agent } from "@pkg/agent-sdk";
|
|
36
|
+
|
|
37
|
+
const researcher = agent({
|
|
38
|
+
name: "researcher",
|
|
39
|
+
model: "openai/gpt-4.1",
|
|
40
|
+
system: "You research topics by fetching web pages.",
|
|
41
|
+
tools: { fetchPage },
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The model sees the tool as `fetchPage` and uses the `description` to decide when to call it.
|
|
46
|
+
|
|
47
|
+
### 3. Add optional configuration
|
|
48
|
+
|
|
49
|
+
| Field | Type | Description |
|
|
50
|
+
| --------------- | ----------------------- | ----------------------------------------------------------------- |
|
|
51
|
+
| `description` | `string` | **Required.** What the tool does. Shown to the model. |
|
|
52
|
+
| `inputSchema` | `ZodType` | **Required.** Validates and types the input from the model. |
|
|
53
|
+
| `execute` | `(input) => Promise<T>` | **Required.** Runs when the model calls the tool. |
|
|
54
|
+
| `outputSchema` | `ZodType` | Optional. Validates the return value before sending to the model. |
|
|
55
|
+
| `title` | `string` | Optional. Human-readable display title for UIs and logs. |
|
|
56
|
+
| `inputExamples` | `Array<{ input: T }>` | Optional. Example inputs to guide the model. |
|
|
57
|
+
|
|
58
|
+
### 4. Add output validation
|
|
59
|
+
|
|
60
|
+
Use `outputSchema` to validate the tool's return value before it is sent back to the model.
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
const calculator = tool({
|
|
64
|
+
description: "Evaluate a math expression",
|
|
65
|
+
inputSchema: z.object({ expression: z.string() }),
|
|
66
|
+
outputSchema: z.object({ result: z.number() }),
|
|
67
|
+
execute: async ({ expression }) => {
|
|
68
|
+
const result = eval(expression); // simplified example
|
|
69
|
+
return { result };
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 5. Add input examples
|
|
75
|
+
|
|
76
|
+
Use `inputExamples` to help the model understand expected input structure. Natively supported by Anthropic; for other providers, examples can be injected into the description via middleware.
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
const searchTool = tool({
|
|
80
|
+
description: "Search the codebase",
|
|
81
|
+
inputSchema: z.object({
|
|
82
|
+
query: z.string().describe("Search query"),
|
|
83
|
+
maxResults: z.number().default(10),
|
|
84
|
+
}),
|
|
85
|
+
inputExamples: [
|
|
86
|
+
{ input: { query: "authentication middleware", maxResults: 5 } },
|
|
87
|
+
{ input: { query: "database connection pool", maxResults: 10 } },
|
|
88
|
+
],
|
|
89
|
+
execute: async ({ query, maxResults }) => {
|
|
90
|
+
return await codeSearch(query, maxResults);
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 6. Destructure input for cleaner code
|
|
96
|
+
|
|
97
|
+
Since `execute` receives the validated input directly, you can destructure it in the function signature.
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
const createFile = tool({
|
|
101
|
+
description: "Create a file with the given content",
|
|
102
|
+
inputSchema: z.object({
|
|
103
|
+
path: z.string(),
|
|
104
|
+
content: z.string(),
|
|
105
|
+
}),
|
|
106
|
+
execute: async ({ path, content }) => {
|
|
107
|
+
await fs.writeFile(path, content);
|
|
108
|
+
return { created: path };
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Verification
|
|
114
|
+
|
|
115
|
+
- The agent calls the tool during `.generate()` when the model decides to use it
|
|
116
|
+
- Check that `result.messages` contains tool call and tool result entries
|
|
117
|
+
- Tool return values appear in the model's context for subsequent reasoning
|
|
118
|
+
|
|
119
|
+
## Troubleshooting
|
|
120
|
+
|
|
121
|
+
### Tool not being called
|
|
122
|
+
|
|
123
|
+
**Fix:** Improve the `description` so the model understands when to use it.
|
|
124
|
+
|
|
125
|
+
### Input validation errors
|
|
126
|
+
|
|
127
|
+
**Fix:** Ensure the `inputSchema` matches what the model is likely to produce.
|
|
128
|
+
|
|
129
|
+
### Tool name mismatch
|
|
130
|
+
|
|
131
|
+
**Fix:** Tool names come from the object key in `tools: { myName: myTool }`, not from the tool definition.
|
|
132
|
+
|
|
133
|
+
## References
|
|
134
|
+
|
|
135
|
+
- [Create an Agent](create-agent.md)
|
|
136
|
+
- [Create a Workflow](create-workflow.md)
|
|
137
|
+
- [Troubleshooting](../troubleshooting.md)
|