@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,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)