@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,374 @@
|
|
|
1
|
+
# Create a Workflow
|
|
2
|
+
|
|
3
|
+
## Prerequisites
|
|
4
|
+
|
|
5
|
+
- `@pkg/agent-sdk` installed
|
|
6
|
+
- `OPENROUTER_API_KEY` environment variable set
|
|
7
|
+
- Familiarity with Zod schemas
|
|
8
|
+
|
|
9
|
+
## Steps
|
|
10
|
+
|
|
11
|
+
### 1. Define a basic workflow
|
|
12
|
+
|
|
13
|
+
A workflow has typed `input` and `output` Zod schemas and a handler function. The handler receives validated input and a `$` step builder for tracked operations.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { workflow } from "@pkg/agent-sdk";
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
|
|
19
|
+
const myWorkflow = workflow(
|
|
20
|
+
{
|
|
21
|
+
name: "data-processor",
|
|
22
|
+
input: z.object({ url: z.url() }),
|
|
23
|
+
output: z.object({ title: z.string(), wordCount: z.number() }),
|
|
24
|
+
},
|
|
25
|
+
async ({ input, $ }) => {
|
|
26
|
+
const page = await $.step({
|
|
27
|
+
id: "fetch-page",
|
|
28
|
+
execute: async () => {
|
|
29
|
+
const res = await fetch(input.url);
|
|
30
|
+
return await res.text();
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (!page.ok) throw new Error(page.error.message);
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
title: input.url,
|
|
38
|
+
wordCount: page.value.split(/\s+/).length,
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 2. Use `$.step` for tracked operations
|
|
45
|
+
|
|
46
|
+
Every `$.step` call is registered in the execution trace. The `execute` callback receives a nested `$` for further composition.
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
const result = await $.step({
|
|
50
|
+
id: "process-data",
|
|
51
|
+
execute: async ({ $ }) => {
|
|
52
|
+
// Nest further tracked operations
|
|
53
|
+
const sub = await $.step({
|
|
54
|
+
id: "sub-task",
|
|
55
|
+
execute: async () => computeResult(),
|
|
56
|
+
});
|
|
57
|
+
return sub.ok ? sub.value : fallback;
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
All `$` methods return `StepResult<T>`. Check `.ok` and access `.value` on success.
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
if (result.ok) {
|
|
66
|
+
console.log(result.value); // the step's return value
|
|
67
|
+
console.log(result.duration); // wall-clock time in ms
|
|
68
|
+
} else {
|
|
69
|
+
console.error(result.error.message);
|
|
70
|
+
console.error(result.error.stepId);
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 3. Use `$.agent` for agent calls
|
|
75
|
+
|
|
76
|
+
Run an agent as a tracked workflow step. The framework records the agent name, input, and output in the trace.
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { agent } from "@pkg/agent-sdk";
|
|
80
|
+
|
|
81
|
+
const analyzer = agent({
|
|
82
|
+
name: "analyzer",
|
|
83
|
+
model: "openai/gpt-4.1",
|
|
84
|
+
input: z.object({ text: z.string() }),
|
|
85
|
+
prompt: ({ input }) => `Analyze this text:\n\n${input.text}`,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const wf = workflow(
|
|
89
|
+
{
|
|
90
|
+
name: "analysis-pipeline",
|
|
91
|
+
input: z.object({ content: z.string() }),
|
|
92
|
+
output: z.object({ analysis: z.string() }),
|
|
93
|
+
},
|
|
94
|
+
async ({ input, $ }) => {
|
|
95
|
+
const result = await $.agent({
|
|
96
|
+
id: "analyze-content",
|
|
97
|
+
agent: analyzer,
|
|
98
|
+
input: { text: input.content },
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (!result.ok) throw new Error(result.error.message);
|
|
102
|
+
|
|
103
|
+
return { analysis: result.value.output };
|
|
104
|
+
},
|
|
105
|
+
);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 4. Use `$.map` for parallel processing
|
|
109
|
+
|
|
110
|
+
Process an array of items concurrently. Results are returned in input order. Use `concurrency` to limit parallelism.
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
const pages = await $.map({
|
|
114
|
+
id: "fetch-pages",
|
|
115
|
+
input: urls,
|
|
116
|
+
concurrency: 3,
|
|
117
|
+
execute: async ({ item: url, index, $ }) => {
|
|
118
|
+
const res = await fetch(url);
|
|
119
|
+
return { url, status: res.status, body: await res.text() };
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (pages.ok) {
|
|
124
|
+
console.log(pages.value); // array of results in input order
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 5. Use `$.all` for heterogeneous concurrent operations
|
|
129
|
+
|
|
130
|
+
`$.all` runs multiple independent operations concurrently, like `Promise.all`. Entries are **factory functions** that receive an `AbortSignal` and return a promise.
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
const results = await $.all({
|
|
134
|
+
id: "parallel-tasks",
|
|
135
|
+
entries: [
|
|
136
|
+
(signal) => fetchMetadata(signal),
|
|
137
|
+
(signal) => fetchContent(signal),
|
|
138
|
+
(signal) =>
|
|
139
|
+
$.step({
|
|
140
|
+
id: "compute",
|
|
141
|
+
execute: async () => heavyComputation(),
|
|
142
|
+
}),
|
|
143
|
+
],
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (results.ok) {
|
|
147
|
+
const [metadata, content, computed] = results.value;
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Entries must be factory functions, not pre-started promises. This ensures the framework controls when work starts and can cancel entries via the signal.
|
|
152
|
+
|
|
153
|
+
### 6. Use `$.race` for first-to-finish
|
|
154
|
+
|
|
155
|
+
`$.race` runs multiple operations concurrently and returns the first to resolve. Losers are cancelled via abort signal. Entries follow the same factory function pattern as `$.all`.
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
const fastest = await $.race({
|
|
159
|
+
id: "fastest-source",
|
|
160
|
+
entries: [(signal) => fetchFromCDN(signal), (signal) => fetchFromOrigin(signal)],
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
if (fastest.ok) {
|
|
164
|
+
console.log(fastest.value); // result from whichever finished first
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### 7. Use other step types
|
|
169
|
+
|
|
170
|
+
| Method | Description |
|
|
171
|
+
| ---------- | ------------------------------------------------------------------- |
|
|
172
|
+
| `$.each` | Sequential side effects. Returns `void`. |
|
|
173
|
+
| `$.reduce` | Sequential accumulation. Each step depends on the previous result. |
|
|
174
|
+
| `$.while` | Conditional loop. Runs while a condition holds. Returns last value. |
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
// $.each - sequential side effects
|
|
178
|
+
await $.each({
|
|
179
|
+
id: "notify-users",
|
|
180
|
+
input: users,
|
|
181
|
+
execute: async ({ item: user }) => {
|
|
182
|
+
await sendNotification(user.email, message);
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// $.reduce - sequential accumulation
|
|
187
|
+
const total = await $.reduce({
|
|
188
|
+
id: "aggregate-scores",
|
|
189
|
+
input: items,
|
|
190
|
+
initial: 0,
|
|
191
|
+
execute: async ({ item, accumulator }) => {
|
|
192
|
+
return accumulator + item.score;
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// $.while - conditional loop
|
|
197
|
+
const converged = await $.while({
|
|
198
|
+
id: "iterate-until-stable",
|
|
199
|
+
condition: ({ value, index }) => index < 10 && (value === undefined || value.delta > 0.01),
|
|
200
|
+
execute: async ({ index }) => {
|
|
201
|
+
return await computeIteration(index);
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### 8. Stream step progress events
|
|
207
|
+
|
|
208
|
+
Use `.stream()` to receive `StepEvent` objects as the workflow executes.
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
const result = await myWorkflow.stream({ url: "https://example.com" });
|
|
212
|
+
|
|
213
|
+
if (result.ok) {
|
|
214
|
+
const reader = result.stream.getReader();
|
|
215
|
+
while (true) {
|
|
216
|
+
const { done, value: event } = await reader.read();
|
|
217
|
+
if (done) break;
|
|
218
|
+
|
|
219
|
+
switch (event.type) {
|
|
220
|
+
case "step:start":
|
|
221
|
+
console.log(`Step started: ${event.step.id}`);
|
|
222
|
+
break;
|
|
223
|
+
case "step:finish":
|
|
224
|
+
console.log(`Step finished: ${event.step.id} (${event.duration}ms)`);
|
|
225
|
+
break;
|
|
226
|
+
case "step:error":
|
|
227
|
+
console.error(`Step failed: ${event.step.id}`, event.error);
|
|
228
|
+
break;
|
|
229
|
+
case "workflow:finish":
|
|
230
|
+
console.log(`Workflow complete (${event.duration}ms)`);
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Final output, trace, and duration are on the result
|
|
236
|
+
console.log(result.output);
|
|
237
|
+
console.log(result.trace);
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### 9. Export as a plain function
|
|
242
|
+
|
|
243
|
+
Use `.fn()` for clean single-function exports.
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
export const processData = myWorkflow.fn();
|
|
247
|
+
|
|
248
|
+
// Callers use it like a regular async function
|
|
249
|
+
const result = await processData({ url: "https://example.com" });
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### 10. Add hooks for observability
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
const wf = workflow(
|
|
256
|
+
{
|
|
257
|
+
name: "observed-workflow",
|
|
258
|
+
input: InputSchema,
|
|
259
|
+
output: OutputSchema,
|
|
260
|
+
onStart: ({ input }) => console.log("Workflow started"),
|
|
261
|
+
onFinish: ({ input, output, duration }) => console.log(`Done in ${duration}ms`),
|
|
262
|
+
onError: ({ input, error }) => console.error("Failed:", error.message),
|
|
263
|
+
onStepStart: ({ step }) => console.log(`Step ${step.id} started`),
|
|
264
|
+
onStepFinish: ({ step, result, duration }) =>
|
|
265
|
+
console.log(`Step ${step.id} done in ${duration}ms`),
|
|
266
|
+
},
|
|
267
|
+
handler,
|
|
268
|
+
);
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Full example
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
import { agent, workflow, tool } from "@pkg/agent-sdk";
|
|
275
|
+
import { z } from "zod";
|
|
276
|
+
|
|
277
|
+
// Define tools
|
|
278
|
+
const fetchPage = tool({
|
|
279
|
+
description: "Fetch a web page",
|
|
280
|
+
inputSchema: z.object({ url: z.url() }),
|
|
281
|
+
execute: async ({ url }) => {
|
|
282
|
+
const res = await fetch(url);
|
|
283
|
+
return { url, body: await res.text() };
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Define agents
|
|
288
|
+
const summarizer = agent({
|
|
289
|
+
name: "summarizer",
|
|
290
|
+
model: "openai/gpt-4.1",
|
|
291
|
+
input: z.object({ text: z.string() }),
|
|
292
|
+
prompt: ({ input }) => `Summarize:\n\n${input.text}`,
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Define workflow
|
|
296
|
+
const pipeline = workflow(
|
|
297
|
+
{
|
|
298
|
+
name: "summarize-pages",
|
|
299
|
+
input: z.object({ urls: z.array(z.url()) }),
|
|
300
|
+
output: z.object({
|
|
301
|
+
summaries: z.array(z.object({ url: z.string(), summary: z.string() })),
|
|
302
|
+
}),
|
|
303
|
+
},
|
|
304
|
+
async ({ input, $ }) => {
|
|
305
|
+
// Fetch all pages in parallel
|
|
306
|
+
const pages = await $.map({
|
|
307
|
+
id: "fetch-pages",
|
|
308
|
+
input: input.urls,
|
|
309
|
+
concurrency: 5,
|
|
310
|
+
execute: async ({ item: url }) => {
|
|
311
|
+
const res = await fetch(url);
|
|
312
|
+
return { url, body: await res.text() };
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
if (!pages.ok) throw new Error("Failed to fetch pages");
|
|
317
|
+
|
|
318
|
+
// Summarize each page with the agent
|
|
319
|
+
const summaries = await $.map({
|
|
320
|
+
id: "summarize-pages",
|
|
321
|
+
input: pages.value,
|
|
322
|
+
concurrency: 3,
|
|
323
|
+
execute: async ({ item: page, $ }) => {
|
|
324
|
+
const result = await $.agent({
|
|
325
|
+
id: `summarize-${page.url}`,
|
|
326
|
+
agent: summarizer,
|
|
327
|
+
input: { text: page.body },
|
|
328
|
+
});
|
|
329
|
+
if (!result.ok) throw new Error(`Failed to summarize ${page.url}`);
|
|
330
|
+
return { url: page.url, summary: result.value.output };
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
if (!summaries.ok) throw new Error("Failed to summarize");
|
|
335
|
+
|
|
336
|
+
return { summaries: summaries.value };
|
|
337
|
+
},
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
export const summarizePages = pipeline.fn();
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Verification
|
|
344
|
+
|
|
345
|
+
- `result.ok` is `true` on success
|
|
346
|
+
- `result.output` contains the validated workflow output
|
|
347
|
+
- `result.trace` contains the frozen execution trace
|
|
348
|
+
- `result.usage` contains aggregated token usage from all `$.agent()` calls
|
|
349
|
+
- `result.duration` contains total wall-clock time in milliseconds
|
|
350
|
+
|
|
351
|
+
## Troubleshooting
|
|
352
|
+
|
|
353
|
+
### Input validation failed
|
|
354
|
+
|
|
355
|
+
**Fix:** Check that the input matches the `input` Zod schema. Ensure all required fields are present and types are correct.
|
|
356
|
+
|
|
357
|
+
### Output validation failed
|
|
358
|
+
|
|
359
|
+
**Fix:** Ensure the handler returns an object matching the `output` Zod schema.
|
|
360
|
+
|
|
361
|
+
### Step result not checked
|
|
362
|
+
|
|
363
|
+
**Fix:** All `$` methods return `StepResult` -- check `.ok` before accessing `.value`.
|
|
364
|
+
|
|
365
|
+
### `$.all`/`$.race` type error
|
|
366
|
+
|
|
367
|
+
**Fix:** Entries must be factory functions `(signal) => Promise`, not pre-started promises.
|
|
368
|
+
|
|
369
|
+
## References
|
|
370
|
+
|
|
371
|
+
- [Create an Agent](create-agent.md)
|
|
372
|
+
- [Create a Tool](create-tool.md)
|
|
373
|
+
- [Provider Overview](../provider/overview.md)
|
|
374
|
+
- [Troubleshooting](../troubleshooting.md)
|
package/docs/overview.md
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# Agent SDK
|
|
2
|
+
|
|
3
|
+
`@pkg/agent-sdk` is a lightweight agent orchestration framework built on the [Vercel AI SDK](https://ai-sdk.dev). It provides typed primitives for creating AI agents, tools, and multi-step workflows with observable execution traces.
|
|
4
|
+
|
|
5
|
+
## Design Principles
|
|
6
|
+
|
|
7
|
+
| Principle | Description |
|
|
8
|
+
| ------------------------------ | ------------------------------------------------------------------------------------------ |
|
|
9
|
+
| Functions all the way down | `agent()`, `tool()`, `workflow()` return plain objects, no classes |
|
|
10
|
+
| Composition over configuration | Combine small functions instead of large option bags |
|
|
11
|
+
| Closures are state | Workflow state is just variables in your handler |
|
|
12
|
+
| Result, never throw | Every public method returns `Result<T>`, callers pattern-match |
|
|
13
|
+
| Zero hidden state | No singletons, no module-level registries |
|
|
14
|
+
| `$` is optional sugar | The `$` helpers register data flow for observability; you can always use plain `for` loops |
|
|
15
|
+
| Context is internal | The framework tracks execution state automatically |
|
|
16
|
+
|
|
17
|
+
## Architecture
|
|
18
|
+
|
|
19
|
+
```mermaid
|
|
20
|
+
%%{init: {
|
|
21
|
+
'theme': 'base',
|
|
22
|
+
'themeVariables': {
|
|
23
|
+
'primaryColor': '#313244',
|
|
24
|
+
'primaryTextColor': '#cdd6f4',
|
|
25
|
+
'primaryBorderColor': '#6c7086',
|
|
26
|
+
'lineColor': '#89b4fa',
|
|
27
|
+
'secondaryColor': '#45475a',
|
|
28
|
+
'tertiaryColor': '#1e1e2e',
|
|
29
|
+
'background': '#1e1e2e',
|
|
30
|
+
'mainBkg': '#313244',
|
|
31
|
+
'clusterBkg': '#1e1e2e',
|
|
32
|
+
'clusterBorder': '#45475a'
|
|
33
|
+
},
|
|
34
|
+
'flowchart': { 'curve': 'basis', 'padding': 15 }
|
|
35
|
+
}}%%
|
|
36
|
+
|
|
37
|
+
flowchart LR
|
|
38
|
+
Input:::external
|
|
39
|
+
|
|
40
|
+
subgraph core [" "]
|
|
41
|
+
tool["tool()"]:::coreNode
|
|
42
|
+
agent["agent()"]:::coreNode
|
|
43
|
+
workflow["workflow()"]:::coreNode
|
|
44
|
+
engine["createWorkflowEngine()"]:::coreNode
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
subgraph steps [" "]
|
|
48
|
+
direction TB
|
|
49
|
+
dollar["$ (StepBuilder)"]:::step
|
|
50
|
+
stepOp["$.step"]:::step
|
|
51
|
+
agentOp["$.agent"]:::step
|
|
52
|
+
mapOp["$.map / $.each"]:::step
|
|
53
|
+
reduceOp["$.reduce / $.while"]:::step
|
|
54
|
+
concOp["$.all / $.race"]:::step
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
subgraph provider [" "]
|
|
58
|
+
direction LR
|
|
59
|
+
OpenRouter:::gateway
|
|
60
|
+
Model:::gateway
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
Input --> agent
|
|
64
|
+
Input --> workflow
|
|
65
|
+
agent -- ".generate() / .stream()" --> Result:::coreNode
|
|
66
|
+
workflow -- ".generate() / .stream()" --> Result
|
|
67
|
+
engine --> workflow
|
|
68
|
+
workflow --> dollar
|
|
69
|
+
dollar --> stepOp & agentOp & mapOp & reduceOp & concOp
|
|
70
|
+
agentOp --> agent
|
|
71
|
+
tool --> agent
|
|
72
|
+
agent --> OpenRouter --> Model
|
|
73
|
+
|
|
74
|
+
classDef external fill:#313244,stroke:#f5c2e7,stroke-width:2px,color:#cdd6f4
|
|
75
|
+
classDef coreNode fill:#313244,stroke:#89b4fa,stroke-width:2px,color:#cdd6f4
|
|
76
|
+
classDef step fill:#313244,stroke:#a6e3a1,stroke-width:2px,color:#cdd6f4
|
|
77
|
+
classDef gateway fill:#313244,stroke:#fab387,stroke-width:2px,color:#cdd6f4
|
|
78
|
+
|
|
79
|
+
style core fill:#181825,stroke:#89b4fa,stroke-width:2px
|
|
80
|
+
style steps fill:#181825,stroke:#a6e3a1,stroke-width:2px
|
|
81
|
+
style provider fill:none,stroke:#fab387,stroke-width:2px,stroke-dasharray:5 5
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Core Concepts
|
|
85
|
+
|
|
86
|
+
### `tool()`
|
|
87
|
+
|
|
88
|
+
Create tools for AI agent function calling. Wraps the AI SDK's `tool()` with `zodSchema()` conversion.
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
const fetchPage = tool({
|
|
92
|
+
description: "Fetch the contents of a web page by URL",
|
|
93
|
+
inputSchema: z.object({ url: z.url() }),
|
|
94
|
+
execute: async ({ url }) => {
|
|
95
|
+
const res = await fetch(url);
|
|
96
|
+
return { url, status: res.status, body: await res.text() };
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### `agent()`
|
|
102
|
+
|
|
103
|
+
Create an agent with typed input, prompt template, tools, subagents, hooks, and `Result` return. Two modes:
|
|
104
|
+
|
|
105
|
+
| Config | `.generate()` first param | How the prompt is built |
|
|
106
|
+
| ---------------------- | ------------------------- | ------------------------------ |
|
|
107
|
+
| `input` + `prompt` set | Typed `TInput` | `prompt({ input })` renders it |
|
|
108
|
+
| Both omitted | `string \| Message[]` | Passed directly to the model |
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
const summarizer = agent({
|
|
112
|
+
name: "summarizer",
|
|
113
|
+
model: "openai/gpt-4.1",
|
|
114
|
+
input: z.object({ text: z.string() }),
|
|
115
|
+
prompt: ({ input }) => `Summarize:\n\n${input.text}`,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const result = await summarizer.generate({ text: "..." });
|
|
119
|
+
if (result.ok) {
|
|
120
|
+
console.log(result.output);
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Every agent exposes `.generate()`, `.stream()`, and `.fn()`.
|
|
125
|
+
|
|
126
|
+
### `workflow()`
|
|
127
|
+
|
|
128
|
+
Create a workflow with typed I/O, `$` step builder, hooks, and execution trace. The handler IS the workflow -- state is just variables.
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
const wf = workflow(
|
|
132
|
+
{
|
|
133
|
+
name: "analyze",
|
|
134
|
+
input: InputSchema,
|
|
135
|
+
output: OutputSchema,
|
|
136
|
+
},
|
|
137
|
+
async ({ input, $ }) => {
|
|
138
|
+
const data = await $.step({
|
|
139
|
+
id: "fetch-data",
|
|
140
|
+
execute: async () => fetchData(input.id),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const result = await $.agent({
|
|
144
|
+
id: "analyze",
|
|
145
|
+
agent: myAgent,
|
|
146
|
+
input: { data: data.value },
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return { data: data.value, analysis: result.ok ? result.output : null };
|
|
150
|
+
},
|
|
151
|
+
);
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The `$` step builder provides tracked operations:
|
|
155
|
+
|
|
156
|
+
| Method | Description |
|
|
157
|
+
| ---------- | ------------------------------------------------------------- |
|
|
158
|
+
| `$.step` | Execute a single unit of work |
|
|
159
|
+
| `$.agent` | Execute an agent call as a tracked operation |
|
|
160
|
+
| `$.map` | Parallel map over items (with optional concurrency limit) |
|
|
161
|
+
| `$.each` | Sequential side effects, returns void |
|
|
162
|
+
| `$.reduce` | Sequential accumulation, each step depends on previous result |
|
|
163
|
+
| `$.while` | Conditional loop, runs while a condition holds |
|
|
164
|
+
| `$.all` | Heterogeneous concurrent operations (like `Promise.all`) |
|
|
165
|
+
| `$.race` | Concurrent operations, first to finish wins |
|
|
166
|
+
|
|
167
|
+
### `createWorkflowEngine()`
|
|
168
|
+
|
|
169
|
+
Create a custom workflow factory that adds additional step types to `$` and/or sets default hooks.
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
const engine = createWorkflowEngine({
|
|
173
|
+
$: {
|
|
174
|
+
retry: async ({ ctx, config }) => {
|
|
175
|
+
// custom step implementation with access to ExecutionContext
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
onStart: ({ input }) => telemetry.trackStart(input),
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Key Types
|
|
183
|
+
|
|
184
|
+
### Result
|
|
185
|
+
|
|
186
|
+
Every public method returns `Result<T>` instead of throwing:
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
type Result<T> = (T & { ok: true }) | { ok: false; error: ResultError };
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Error codes: `VALIDATION_ERROR`, `AGENT_ERROR`, `WORKFLOW_ERROR`, `ABORT_ERROR`. Helpers: `ok()`, `err()`, `isOk()`, `isErr()`.
|
|
193
|
+
|
|
194
|
+
### Runnable
|
|
195
|
+
|
|
196
|
+
Both `Agent` and `Workflow` satisfy the `Runnable` interface, enabling composition. Subagents passed to `agent({ agents })` are automatically wrapped as callable tools.
|
|
197
|
+
|
|
198
|
+
### Context
|
|
199
|
+
|
|
200
|
+
Internal -- never exposed to users. The framework creates it automatically. Custom step factories (via `createWorkflowEngine`) receive `ExecutionContext` with `signal` and `log`.
|
|
201
|
+
|
|
202
|
+
### Logger
|
|
203
|
+
|
|
204
|
+
Pino-compatible interface with `child()` support. The framework creates scoped child loggers at each boundary (workflow, step, agent).
|
|
205
|
+
|
|
206
|
+
## Provider
|
|
207
|
+
|
|
208
|
+
OpenRouter integration for model resolution. The `Model` type accepted by `agent()` is `string | LanguageModel` -- string IDs are resolved via OpenRouter at runtime, or pass any AI SDK provider instance directly.
|
|
209
|
+
|
|
210
|
+
| Export | Description |
|
|
211
|
+
| ---------------------------- | ---------------------------------------------------------------- |
|
|
212
|
+
| `openrouter(modelId)` | Returns a `LanguageModel` (cached provider, reused across calls) |
|
|
213
|
+
| `createOpenRouter(options?)` | Create a new OpenRouter provider instance |
|
|
214
|
+
| `model(id)` | Look up a `ModelDefinition` by ID (throws if not found) |
|
|
215
|
+
| `tryModel(id)` | Look up a `ModelDefinition` by ID (returns `undefined`) |
|
|
216
|
+
| `models(filter?)` | Return all model definitions, optionally filtered |
|
|
217
|
+
|
|
218
|
+
## Execution Trace
|
|
219
|
+
|
|
220
|
+
Workflows produce a frozen `TraceEntry[]` tree representing every tracked `$` operation:
|
|
221
|
+
|
|
222
|
+
| Field | Type | Description |
|
|
223
|
+
| ------------ | --------------- | ------------------------------------------------------------------- |
|
|
224
|
+
| `id` | `string` | Step ID from the `$` config |
|
|
225
|
+
| `type` | `OperationType` | `step`, `agent`, `map`, `each`, `reduce`, `while`, `all`, or `race` |
|
|
226
|
+
| `startedAt` | `number` | Unix milliseconds |
|
|
227
|
+
| `finishedAt` | `number?` | Unix milliseconds (undefined while running) |
|
|
228
|
+
| `error` | `Error?` | Present on failure |
|
|
229
|
+
| `usage` | `TokenUsage?` | Token usage (populated for successful agent steps) |
|
|
230
|
+
| `children` | `TraceEntry[]?` | Nested operations (iterations, sub-steps) |
|
|
231
|
+
|
|
232
|
+
## References
|
|
233
|
+
|
|
234
|
+
- [Agent](core/agent.md)
|
|
235
|
+
- [Workflow](core/workflow.md)
|
|
236
|
+
- [Step Builder ($)](core/step.md)
|
|
237
|
+
- [Tools](core/tools.md)
|
|
238
|
+
- [Hooks](core/hooks.md)
|
|
239
|
+
- [Provider](provider/overview.md)
|
|
240
|
+
- [Models](provider/models.md)
|
|
241
|
+
- [Token Usage](provider/usage.md)
|
|
242
|
+
- [Create an Agent](guides/create-agent.md)
|
|
243
|
+
- [Create a Workflow](guides/create-workflow.md)
|
|
244
|
+
- [Create a Tool](guides/create-tool.md)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Models
|
|
2
|
+
|
|
3
|
+
The SDK includes a model catalog with metadata and pricing for supported OpenRouter models. Used for cost calculation and model selection.
|
|
4
|
+
|
|
5
|
+
Model data is auto-generated from the OpenRouter API. Run `pnpm --filter=@pkg/agent-sdk generate:models` to refresh.
|
|
6
|
+
|
|
7
|
+
## Model Definition
|
|
8
|
+
|
|
9
|
+
Each model entry has:
|
|
10
|
+
|
|
11
|
+
| Field | Type | Description |
|
|
12
|
+
| ---------- | --------------- | --------------------------------------------- |
|
|
13
|
+
| `id` | `string` | OpenRouter model ID (e.g. `'openai/gpt-4.1'`) |
|
|
14
|
+
| `category` | `ModelCategory` | `'chat'`, `'coding'`, or `'reasoning'` |
|
|
15
|
+
| `pricing` | `ModelPricing` | Per-token rates (OpenRouter convention) |
|
|
16
|
+
|
|
17
|
+
## Pricing
|
|
18
|
+
|
|
19
|
+
| Field | Type | Description |
|
|
20
|
+
| ------------------- | -------- | ------------------------------------- |
|
|
21
|
+
| `prompt` | `number` | USD per input token |
|
|
22
|
+
| `completion` | `number` | USD per output token |
|
|
23
|
+
| `inputCacheRead` | `number` | USD per cached input token (optional) |
|
|
24
|
+
| `inputCacheWrite` | `number` | USD per cached input write (optional) |
|
|
25
|
+
| `webSearch` | `number` | USD per web search request (optional) |
|
|
26
|
+
| `internalReasoning` | `number` | USD per reasoning token (optional) |
|
|
27
|
+
| `image` | `number` | USD per image input token (optional) |
|
|
28
|
+
|
|
29
|
+
## Lookup
|
|
30
|
+
|
|
31
|
+
Three functions:
|
|
32
|
+
|
|
33
|
+
- `model(id)` -- Returns a single `ModelDefinition` or throws if the ID is not in the catalog.
|
|
34
|
+
- `tryModel(id)` -- Returns a single `ModelDefinition` or `undefined` if the ID is not in the catalog.
|
|
35
|
+
- `models(filter?)` -- Returns model definitions, optionally filtered by a predicate.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { model, tryModel, models } from "@pkg/agent-sdk";
|
|
39
|
+
|
|
40
|
+
const m = model("openai/gpt-4.1");
|
|
41
|
+
console.log(m.pricing.prompt); // cost per input token
|
|
42
|
+
console.log(m.category); // 'chat'
|
|
43
|
+
|
|
44
|
+
const all = models();
|
|
45
|
+
const reasoning = models((m) => m.category === "reasoning");
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Adding a Model
|
|
49
|
+
|
|
50
|
+
Add an entry to `models.config.json` at the package root with the OpenRouter model ID and category, then run `pnpm --filter=@pkg/agent-sdk generate:models` to fetch pricing from the API.
|
|
51
|
+
|
|
52
|
+
## References
|
|
53
|
+
|
|
54
|
+
- [Provider Overview](overview.md)
|
|
55
|
+
- [Token Usage](usage.md)
|