@funkai/agents 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/.generated/req.txt +1 -0
  2. package/.turbo/turbo-build.log +21 -0
  3. package/.turbo/turbo-test$colon$coverage.log +109 -0
  4. package/.turbo/turbo-test.log +141 -0
  5. package/.turbo/turbo-typecheck.log +4 -0
  6. package/CHANGELOG.md +16 -0
  7. package/ISSUES.md +540 -0
  8. package/LICENSE +21 -0
  9. package/README.md +128 -0
  10. package/banner.svg +97 -0
  11. package/coverage/lcov-report/base.css +224 -0
  12. package/coverage/lcov-report/block-navigation.js +87 -0
  13. package/coverage/lcov-report/core/agents/base/agent.ts.html +1705 -0
  14. package/coverage/lcov-report/core/agents/base/index.html +146 -0
  15. package/coverage/lcov-report/core/agents/base/output.ts.html +256 -0
  16. package/coverage/lcov-report/core/agents/base/utils.ts.html +694 -0
  17. package/coverage/lcov-report/core/agents/flow/engine.ts.html +928 -0
  18. package/coverage/lcov-report/core/agents/flow/flow-agent.ts.html +1462 -0
  19. package/coverage/lcov-report/core/agents/flow/index.html +146 -0
  20. package/coverage/lcov-report/core/agents/flow/messages.ts.html +508 -0
  21. package/coverage/lcov-report/core/agents/flow/steps/factory.ts.html +1975 -0
  22. package/coverage/lcov-report/core/agents/flow/steps/index.html +116 -0
  23. package/coverage/lcov-report/core/index.html +131 -0
  24. package/coverage/lcov-report/core/logger.ts.html +541 -0
  25. package/coverage/lcov-report/core/models/providers/index.html +116 -0
  26. package/coverage/lcov-report/core/models/providers/openai.ts.html +337 -0
  27. package/coverage/lcov-report/core/provider/index.html +131 -0
  28. package/coverage/lcov-report/core/provider/provider.ts.html +346 -0
  29. package/coverage/lcov-report/core/provider/usage.ts.html +376 -0
  30. package/coverage/lcov-report/core/tool.ts.html +577 -0
  31. package/coverage/lcov-report/favicon.png +0 -0
  32. package/coverage/lcov-report/index.html +221 -0
  33. package/coverage/lcov-report/lib/hooks.ts.html +262 -0
  34. package/coverage/lcov-report/lib/index.html +161 -0
  35. package/coverage/lcov-report/lib/middleware.ts.html +274 -0
  36. package/coverage/lcov-report/lib/runnable.ts.html +151 -0
  37. package/coverage/lcov-report/lib/trace.ts.html +520 -0
  38. package/coverage/lcov-report/prettify.css +1 -0
  39. package/coverage/lcov-report/prettify.js +2 -0
  40. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  41. package/coverage/lcov-report/sorter.js +210 -0
  42. package/coverage/lcov-report/utils/attempt.ts.html +199 -0
  43. package/coverage/lcov-report/utils/error.ts.html +421 -0
  44. package/coverage/lcov-report/utils/index.html +176 -0
  45. package/coverage/lcov-report/utils/resolve.ts.html +208 -0
  46. package/coverage/lcov-report/utils/result.ts.html +538 -0
  47. package/coverage/lcov-report/utils/zod.ts.html +178 -0
  48. package/coverage/lcov.info +1566 -0
  49. package/dist/index.d.mts +2883 -0
  50. package/dist/index.d.mts.map +1 -0
  51. package/dist/index.mjs +2312 -0
  52. package/dist/index.mjs.map +1 -0
  53. package/docs/core/agent.md +231 -0
  54. package/docs/core/hooks.md +95 -0
  55. package/docs/core/overview.md +87 -0
  56. package/docs/core/step.md +279 -0
  57. package/docs/core/tools.md +98 -0
  58. package/docs/core/workflow.md +235 -0
  59. package/docs/guides/create-agent.md +224 -0
  60. package/docs/guides/create-tool.md +137 -0
  61. package/docs/guides/create-workflow.md +374 -0
  62. package/docs/overview.md +244 -0
  63. package/docs/provider/models.md +55 -0
  64. package/docs/provider/overview.md +106 -0
  65. package/docs/provider/usage.md +100 -0
  66. package/docs/research/experimental-context.md +167 -0
  67. package/docs/research/gap-analysis.md +86 -0
  68. package/docs/research/prepare-step-and-active-tools.md +138 -0
  69. package/docs/research/sub-agent-model.md +249 -0
  70. package/docs/troubleshooting.md +60 -0
  71. package/logo.svg +17 -0
  72. package/models.config.json +18 -0
  73. package/package.json +60 -0
  74. package/scripts/generate-models.ts +324 -0
  75. package/src/core/agents/base/agent.test.ts +1522 -0
  76. package/src/core/agents/base/agent.ts +547 -0
  77. package/src/core/agents/base/output.test.ts +93 -0
  78. package/src/core/agents/base/output.ts +57 -0
  79. package/src/core/agents/base/types.test-d.ts +69 -0
  80. package/src/core/agents/base/types.ts +503 -0
  81. package/src/core/agents/base/utils.test.ts +397 -0
  82. package/src/core/agents/base/utils.ts +197 -0
  83. package/src/core/agents/flow/engine.test.ts +452 -0
  84. package/src/core/agents/flow/engine.ts +281 -0
  85. package/src/core/agents/flow/flow-agent.test.ts +1027 -0
  86. package/src/core/agents/flow/flow-agent.ts +473 -0
  87. package/src/core/agents/flow/messages.test.ts +198 -0
  88. package/src/core/agents/flow/messages.ts +141 -0
  89. package/src/core/agents/flow/steps/agent.test.ts +280 -0
  90. package/src/core/agents/flow/steps/agent.ts +87 -0
  91. package/src/core/agents/flow/steps/all.test.ts +300 -0
  92. package/src/core/agents/flow/steps/all.ts +73 -0
  93. package/src/core/agents/flow/steps/builder.ts +124 -0
  94. package/src/core/agents/flow/steps/each.test.ts +257 -0
  95. package/src/core/agents/flow/steps/each.ts +61 -0
  96. package/src/core/agents/flow/steps/factory.test-d.ts +50 -0
  97. package/src/core/agents/flow/steps/factory.test.ts +1025 -0
  98. package/src/core/agents/flow/steps/factory.ts +645 -0
  99. package/src/core/agents/flow/steps/map.test.ts +273 -0
  100. package/src/core/agents/flow/steps/map.ts +75 -0
  101. package/src/core/agents/flow/steps/race.test.ts +290 -0
  102. package/src/core/agents/flow/steps/race.ts +59 -0
  103. package/src/core/agents/flow/steps/reduce.test.ts +310 -0
  104. package/src/core/agents/flow/steps/reduce.ts +73 -0
  105. package/src/core/agents/flow/steps/result.ts +27 -0
  106. package/src/core/agents/flow/steps/step.test.ts +402 -0
  107. package/src/core/agents/flow/steps/step.ts +51 -0
  108. package/src/core/agents/flow/steps/while.test.ts +283 -0
  109. package/src/core/agents/flow/steps/while.ts +75 -0
  110. package/src/core/agents/flow/types.ts +348 -0
  111. package/src/core/logger.test.ts +163 -0
  112. package/src/core/logger.ts +152 -0
  113. package/src/core/models/index.test.ts +137 -0
  114. package/src/core/models/index.ts +152 -0
  115. package/src/core/models/providers/openai.ts +84 -0
  116. package/src/core/provider/provider.test.ts +128 -0
  117. package/src/core/provider/provider.ts +99 -0
  118. package/src/core/provider/types.ts +98 -0
  119. package/src/core/provider/usage.test.ts +304 -0
  120. package/src/core/provider/usage.ts +97 -0
  121. package/src/core/tool.test.ts +65 -0
  122. package/src/core/tool.ts +164 -0
  123. package/src/core/types.ts +66 -0
  124. package/src/index.ts +95 -0
  125. package/src/lib/context.test.ts +86 -0
  126. package/src/lib/context.ts +49 -0
  127. package/src/lib/hooks.test.ts +102 -0
  128. package/src/lib/hooks.ts +59 -0
  129. package/src/lib/middleware.test.ts +122 -0
  130. package/src/lib/middleware.ts +63 -0
  131. package/src/lib/runnable.test.ts +41 -0
  132. package/src/lib/runnable.ts +22 -0
  133. package/src/lib/trace.test.ts +291 -0
  134. package/src/lib/trace.ts +145 -0
  135. package/src/models/index.ts +123 -0
  136. package/src/models/providers/index.ts +15 -0
  137. package/src/models/providers/openai.ts +84 -0
  138. package/src/testing/context.ts +32 -0
  139. package/src/testing/index.ts +2 -0
  140. package/src/testing/logger.ts +19 -0
  141. package/src/utils/attempt.test.ts +127 -0
  142. package/src/utils/attempt.ts +38 -0
  143. package/src/utils/error.test.ts +179 -0
  144. package/src/utils/error.ts +112 -0
  145. package/src/utils/resolve.test.ts +38 -0
  146. package/src/utils/resolve.ts +41 -0
  147. package/src/utils/result.test.ts +79 -0
  148. package/src/utils/result.ts +151 -0
  149. package/src/utils/zod.test.ts +69 -0
  150. package/src/utils/zod.ts +31 -0
  151. package/tsconfig.json +25 -0
  152. package/tsdown.config.ts +15 -0
  153. package/vitest.config.ts +46 -0
@@ -0,0 +1,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)
@@ -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)