@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,106 @@
1
+ # Provider Overview
2
+
3
+ The provider module integrates with OpenRouter for model access and provides a model catalog with pricing data.
4
+
5
+ ## OpenRouter
6
+
7
+ All models are accessed via OpenRouter. The `openrouter()` function creates a language model from a model ID.
8
+
9
+ ```ts
10
+ import { openrouter } from "@pkg/agent-sdk";
11
+
12
+ const m = openrouter("openai/gpt-4.1");
13
+ ```
14
+
15
+ The provider instance is cached at module scope and reused across calls. If `OPENROUTER_API_KEY` changes at runtime, the cache is invalidated and a new provider is created.
16
+
17
+ ## API Key
18
+
19
+ Resolved from the `OPENROUTER_API_KEY` environment variable. Throws if not set.
20
+
21
+ ```ts
22
+ // Override with a custom provider instance
23
+ import { createOpenRouter } from "@pkg/agent-sdk";
24
+
25
+ const provider = createOpenRouter({ apiKey: "sk-..." });
26
+ const m = provider("openai/gpt-4.1");
27
+ ```
28
+
29
+ ## Model Catalog
30
+
31
+ Models are defined in `models.config.json` and auto-generated into provider-specific files. Use the catalog functions to look up model definitions and pricing.
32
+
33
+ ```ts
34
+ import { model, tryModel, models } from "@pkg/agent-sdk";
35
+
36
+ // Look up a model (throws if not found)
37
+ const gpt4 = model("openai/gpt-4.1");
38
+ console.log(gpt4.pricing.prompt); // cost per input token
39
+
40
+ // Safe lookup (returns undefined if not found)
41
+ const maybe = tryModel("openai/gpt-4.1");
42
+
43
+ // List all models, optionally filtered
44
+ const allModels = models();
45
+ const reasoningModels = models((m) => m.category === "reasoning");
46
+ ```
47
+
48
+ ## Token Usage
49
+
50
+ Aggregate token counts across agent and workflow executions.
51
+
52
+ ```ts
53
+ import { agentUsage, workflowUsage } from "@pkg/agent-sdk";
54
+
55
+ // Single agent usage
56
+ const usage = agentUsage("my-agent", tokenRecords);
57
+ console.log(usage.inputTokens, usage.outputTokens, usage.totalTokens);
58
+
59
+ // Workflow usage with per-agent breakdown
60
+ const wfUsage = workflowUsage(allTokenRecords);
61
+ for (const entry of wfUsage.usages) {
62
+ console.log(`${entry.agentId}: ${entry.totalTokens} tokens`);
63
+ }
64
+ ```
65
+
66
+ ## Exports
67
+
68
+ | Export | Description |
69
+ | ------------------------------ | ----------------------------------------------------------------- |
70
+ | `openrouter(modelId)` | Create a language model from OpenRouter (cached provider) |
71
+ | `createOpenRouter(options?)` | Create a custom OpenRouter provider instance |
72
+ | `model(id)` | Look up a model definition from the catalog (throws if not found) |
73
+ | `tryModel(id)` | Look up a model definition (returns `undefined` if not found) |
74
+ | `models(filter?)` | Get model definitions, optionally filtered by predicate |
75
+ | `agentUsage(agentId, records)` | Aggregate token counts for a single agent |
76
+ | `workflowUsage(records)` | Aggregate token counts for a workflow with per-agent breakdown |
77
+
78
+ ## Model Reference
79
+
80
+ String model IDs passed to `agent()` or `openrouter()` are resolved via OpenRouter at runtime. You can also pass an AI SDK `LanguageModel` instance directly.
81
+
82
+ ```ts
83
+ import { agent } from "@pkg/agent-sdk";
84
+
85
+ // String ID -- resolved via OpenRouter
86
+ const a1 = agent({
87
+ name: "my-agent",
88
+ model: "openai/gpt-4.1",
89
+ system: "You are helpful.",
90
+ });
91
+
92
+ // AI SDK provider instance -- bypasses OpenRouter
93
+ import { openai } from "@ai-sdk/openai";
94
+
95
+ const a2 = agent({
96
+ name: "my-agent",
97
+ model: openai("gpt-4.1"),
98
+ system: "You are helpful.",
99
+ });
100
+ ```
101
+
102
+ ## References
103
+
104
+ - [Models](models.md)
105
+ - [Create an Agent](../guides/create-agent.md)
106
+ - [Troubleshooting](../troubleshooting.md)
@@ -0,0 +1,100 @@
1
+ # Token Usage
2
+
3
+ Token tracking and aggregation for agent and workflow executions.
4
+
5
+ ## TokenUsageRecord
6
+
7
+ Raw tracking record from a single model invocation. Fields are `number | undefined` because not all providers report every field.
8
+
9
+ | Field | Type | Description |
10
+ | ------------------ | --------------------- | ---------------------------------------- |
11
+ | `modelId` | `string` | OpenRouter model ID |
12
+ | `inputTokens` | `number \| undefined` | Input (prompt) tokens |
13
+ | `outputTokens` | `number \| undefined` | Output (completion) tokens |
14
+ | `totalTokens` | `number \| undefined` | Input + output |
15
+ | `cacheReadTokens` | `number \| undefined` | Tokens served from provider prompt cache |
16
+ | `cacheWriteTokens` | `number \| undefined` | Tokens written to prompt cache |
17
+ | `reasoningTokens` | `number \| undefined` | Internal reasoning tokens (e.g. o3/o4) |
18
+ | `source` | `object \| undefined` | Framework-populated source info |
19
+
20
+ The `source` field identifies which component produced the record:
21
+
22
+ ```ts
23
+ source?: {
24
+ workflowId?: string
25
+ stepId?: string
26
+ agentId: string
27
+ scope: string[]
28
+ }
29
+ ```
30
+
31
+ ## Agent Usage
32
+
33
+ `agentUsage()` aggregates token counts from one or more raw records into a flat `AgentTokenUsage` object.
34
+
35
+ ```ts
36
+ import { agentUsage } from "@pkg/agent-sdk";
37
+
38
+ const usage = agentUsage("my-agent", records);
39
+ console.log(usage.agentId); // 'my-agent'
40
+ console.log(usage.inputTokens); // resolved number (0 if undefined)
41
+ console.log(usage.outputTokens);
42
+ console.log(usage.totalTokens);
43
+ ```
44
+
45
+ ## Workflow Usage
46
+
47
+ `workflowUsage()` groups records by `source.agentId` and computes per-agent usage.
48
+
49
+ ```ts
50
+ import { workflowUsage } from "@pkg/agent-sdk";
51
+
52
+ const usage = workflowUsage(allRecords);
53
+ for (const entry of usage.usages) {
54
+ console.log(`${entry.agentId}: ${entry.totalTokens} tokens`);
55
+ }
56
+ ```
57
+
58
+ ## TokenUsage (resolved)
59
+
60
+ The aggregated output type. All fields are resolved `number` (0 when the raw record was `undefined`).
61
+
62
+ | Field | Type | Description |
63
+ | ------------------ | -------- | ------------------------- |
64
+ | `inputTokens` | `number` | Total input tokens |
65
+ | `outputTokens` | `number` | Total output tokens |
66
+ | `totalTokens` | `number` | Input + output |
67
+ | `cacheReadTokens` | `number` | Cached input tokens |
68
+ | `cacheWriteTokens` | `number` | Cache write tokens |
69
+ | `reasoningTokens` | `number` | Internal reasoning tokens |
70
+
71
+ ## Usage Utilities
72
+
73
+ ### `sumTokenUsage()`
74
+
75
+ Sum multiple `TokenUsage` objects into a new one. Pure function, does not mutate inputs.
76
+
77
+ ```ts
78
+ import { sumTokenUsage } from "@pkg/agent-sdk";
79
+
80
+ const total = sumTokenUsage([usageA, usageB, usageC]);
81
+ ```
82
+
83
+ ### `collectUsages()`
84
+
85
+ Walk a `TraceEntry[]` tree and collect all `usage` values into a flat array (recursively including children). Compose with `sumTokenUsage()` to aggregate usage across all `$.agent()` calls.
86
+
87
+ ```ts
88
+ import { collectUsages, sumTokenUsage } from "@pkg/agent-sdk";
89
+
90
+ const result = await myWorkflow.generate(input);
91
+ if (result.ok) {
92
+ // result.usage is already computed, but you can also derive it from the trace:
93
+ const usage = sumTokenUsage(collectUsages(result.trace));
94
+ }
95
+ ```
96
+
97
+ ## References
98
+
99
+ - [Models](models.md)
100
+ - [Provider Overview](overview.md)
@@ -0,0 +1,167 @@
1
+ # Research: `experimental_context` — Per-Call Context Store
2
+
3
+ > **Date**: 2026-03-06
4
+ > **AI SDK Version**: `^6.0.111`
5
+ > **Status**: Gap identified — context is not wired through the SDK; tool execute signature drops it
6
+
7
+ ## Summary
8
+
9
+ The Vercel AI SDK v6 provides `experimental_context` — a user-defined state object that flows through the entire `generateText`/`streamText` lifecycle. It is available in `prepareStep`, tool `execute` callbacks, and lifecycle hooks. Our SDK does not expose it at any layer, and our `tool()` helper actively drops the second argument that carries it.
10
+
11
+ ## How `experimental_context` Flows in the AI SDK
12
+
13
+ ```
14
+ generateText({ experimental_context: initialState })
15
+
16
+
17
+ prepareStep({ experimental_context })
18
+ │ can return { experimental_context: modifiedState }
19
+
20
+ tool.execute(input, { experimental_context })
21
+ │ tools can read it (typed as unknown)
22
+
23
+ onStepFinish({ experimental_context })
24
+ │ callbacks can observe it
25
+
26
+ next step's prepareStep receives updated context
27
+
28
+
29
+ result.experimental_context ← final state after all steps
30
+ ```
31
+
32
+ ### Key Behaviors
33
+
34
+ - **Mutable across steps**: `prepareStep` can return a new `experimental_context`, which propagates to all subsequent steps and tool calls
35
+ - **Available in tool execute**: received as `execute(input, { experimental_context })`
36
+ - **Typed as `unknown`**: consumers must cast/validate at runtime
37
+ - **Experimental**: can break in patch releases (the `experimental_` prefix is the AI SDK's convention for unstable APIs)
38
+
39
+ ### AI SDK Code Example
40
+
41
+ ```typescript
42
+ const result = await generateText({
43
+ model: openrouter("anthropic/claude-sonnet-4"),
44
+ tools: {
45
+ fetchData: tool({
46
+ description: "Fetch data from the API",
47
+ inputSchema: z.object({ endpoint: z.string() }),
48
+ execute: async (input, { experimental_context: ctx }) => {
49
+ const typed = ctx as { authToken: string };
50
+ const res = await fetch(input.endpoint, {
51
+ headers: { Authorization: `Bearer ${typed.authToken}` },
52
+ });
53
+ return res.json();
54
+ },
55
+ }),
56
+ },
57
+ experimental_context: { authToken: "sk-..." },
58
+ prepareStep: async ({ experimental_context, stepNumber }) => {
59
+ // Evolve context between steps
60
+ const ctx = experimental_context as { authToken: string; stepCount: number };
61
+ return {
62
+ experimental_context: { ...ctx, stepCount: stepNumber },
63
+ };
64
+ },
65
+ });
66
+ ```
67
+
68
+ ## Current SDK State
69
+
70
+ ### `tool.ts` — Execute signature drops context
71
+
72
+ Our `tool()` helper wraps execute as:
73
+
74
+ ```typescript
75
+ // tool.ts:111
76
+ execute: async (data: TInput) => config.execute(data),
77
+ ```
78
+
79
+ The AI SDK passes a second `options` argument to `execute` containing `{ experimental_context, abortSignal, messages, ... }`. Our wrapper discards this entirely. Tools created with our `tool()` can never access context.
80
+
81
+ ### `ToolConfig` — No context in the type
82
+
83
+ ```typescript
84
+ // tool.ts:65
85
+ execute: (input: TInput) => Promise<TOutput>;
86
+ ```
87
+
88
+ The execute signature only accepts `input`. There is no options parameter.
89
+
90
+ ### `AgentConfig` / `AgentOverrides` — No context field
91
+
92
+ Neither type includes `experimental_context` or any equivalent. The `generateText` call in `agent.ts` does not pass it.
93
+
94
+ ### Comparison: Our `ExecutionContext` vs AI SDK `experimental_context`
95
+
96
+ | Aspect | AI SDK `experimental_context` | Our `ExecutionContext` |
97
+ | ------------------------------ | ---------------------------------- | --------------------------------------------------- |
98
+ | **Layer** | Per-`generateText` call, per-step | Per-workflow, per-`$` operation |
99
+ | **Available in tools** | Yes, via execute's second arg | No |
100
+ | **Mutable between steps** | Yes, via `prepareStep` | No (readonly signal + logger) |
101
+ | **Available in `prepareStep`** | Yes | N/A (no `prepareStep` exposed) |
102
+ | **User-defined shape** | Yes (typed as `unknown`) | No (fixed: signal + logger + trace) |
103
+ | **Purpose** | Ambient state for the agentic loop | Framework plumbing (cancellation, logging, tracing) |
104
+
105
+ These are complementary, not competing. `ExecutionContext` is framework infrastructure; `experimental_context` is user-space state.
106
+
107
+ ## Implementation Path
108
+
109
+ ### 1. Extend `ToolConfig.execute` to accept context
110
+
111
+ ```typescript
112
+ interface ToolConfig<TInput, TOutput> {
113
+ execute: (input: TInput, options?: { context?: unknown }) => Promise<TOutput>;
114
+ }
115
+ ```
116
+
117
+ And forward the AI SDK's second arg:
118
+
119
+ ```typescript
120
+ // tool.ts — updated wrapper
121
+ execute: async (data: TInput, options) => config.execute(data, {
122
+ context: options?.experimental_context,
123
+ }),
124
+ ```
125
+
126
+ ### 2. Add context to `AgentConfig` and `AgentOverrides`
127
+
128
+ ```typescript
129
+ interface AgentConfig<TInput, TOutput, TTools, TSubAgents> {
130
+ // ...existing fields
131
+ context?: unknown;
132
+ }
133
+
134
+ interface AgentOverrides<TTools, TSubAgents> {
135
+ // ...existing fields
136
+ context?: unknown;
137
+ }
138
+ ```
139
+
140
+ ### 3. Forward to `generateText`
141
+
142
+ ```typescript
143
+ const aiResult = await generateText({
144
+ // ...existing params
145
+ experimental_context: overrideContext ?? config.context,
146
+ });
147
+ ```
148
+
149
+ ### 4. Combine with `prepareStep`
150
+
151
+ Once both `prepareStep` and `experimental_context` are wired, the context becomes fully mutable across the agentic loop — enabling patterns like:
152
+
153
+ - Accumulating discovered information across steps
154
+ - Passing auth tokens or session state to tools
155
+ - Tracking which tools have been called and their results
156
+ - Sharing state between parent agent and sub-agents-as-tools
157
+
158
+ ## Open Issues
159
+
160
+ - [GitHub #10482](https://github.com/vercel/ai/issues/10482) — Requests tighter `experimental_context` integration in `prepareStep` (may already be resolved in latest v6)
161
+ - The `experimental_` prefix means this API could change. Consider wrapping it behind a stable SDK-level abstraction so consumers are insulated from upstream changes.
162
+
163
+ ## References
164
+
165
+ - [AI SDK Tool Calling docs](https://ai-sdk.dev/docs/ai-sdk-core/tools-and-tool-calling)
166
+ - [generateText API Reference](https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-text)
167
+ - [GitHub Issue #10482](https://github.com/vercel/ai/issues/10482)
@@ -0,0 +1,86 @@
1
+ # Research: Agent SDK Gap Analysis
2
+
3
+ > **Date**: 2026-03-06
4
+ > **AI SDK Version**: `^6.0.111`
5
+
6
+ ## Overview
7
+
8
+ This document consolidates all identified gaps between our agent SDK wrapper and the underlying Vercel AI SDK v6 capabilities, specifically for supporting agentic loop control, context management, and sub-agent orchestration.
9
+
10
+ For detailed analysis of each area, see:
11
+
12
+ - [prepareStep and activeTools](./prepare-step-and-active-tools.md)
13
+ - [experimental_context](./experimental-context.md)
14
+ - [Sub-Agent Model](./sub-agent-model.md)
15
+
16
+ ## Gap Summary
17
+
18
+ | # | Gap | Severity | Current State | AI SDK Capability |
19
+ | --- | -------------------------------------- | ---------- | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
20
+ | 1 | No `prepareStep` support | **High** | `AgentConfig`/`AgentOverrides` have no field; `agent.ts` does not pass it to `generateText`/`streamText` | `prepareStep` callback fires before each step; can modify model, messages, tools, system prompt, context |
21
+ | 2 | No `activeTools` support | **High** | Not exposed at any layer | Top-level and per-step tool restriction via string array of tool names |
22
+ | 3 | No `experimental_context` support | **High** | Not exposed; `tool()` drops the second execute arg that carries it | User-defined state flows through `prepareStep`, tool `execute`, and lifecycle hooks |
23
+ | 4 | `tool()` drops AI SDK execute options | **High** | `tool.ts:111` wraps as `(data) => config.execute(data)`, discarding options | Second arg provides `{ experimental_context, abortSignal, messages, toolCallId }` |
24
+ | 5 | No context forwarding to sub-agents | **Medium** | Sub-agents start fresh with only their `input` | `experimental_context` could carry parent state to child tools |
25
+ | 6 | No dynamic sub-agent spawning | **Medium** | `agents` config is static at creation time | `prepareStep` + `activeTools` can dynamically add/remove tools per step |
26
+ | 7 | No shared state between sibling agents | **Medium** | Sub-agents are fully isolated | `experimental_context` accumulation via `prepareStep` |
27
+ | 8 | No per-delegation step budget | **Low** | Child agents use their own `maxSteps` (default 20) | `AgentOverrides.maxSteps` exists but isn't used in sub-agent wrapper |
28
+ | 9 | No result-to-context feedback | **Medium** | Sub-agent output is only a tool result string | `prepareStep` can accumulate tool results into context between steps |
29
+
30
+ ## Impact on Use Cases
31
+
32
+ ### Agentic Loop Control
33
+
34
+ Without `prepareStep`, the agentic loop is fully model-driven with no programmatic intervention between steps. Cannot:
35
+
36
+ - Compress context when it grows too large (token budget management)
37
+ - Restrict tools to specific phases of execution
38
+ - Switch models based on task complexity
39
+ - Adapt system prompts based on progress
40
+
41
+ ### Sub-Agent Orchestration
42
+
43
+ Without context forwarding and `prepareStep`, sub-agents operate in isolation. Cannot:
44
+
45
+ - Pass parent conversation context to child agents
46
+ - Accumulate findings across multiple delegations
47
+ - Dynamically decide which sub-agents to make available
48
+ - Coordinate sibling agents through shared state
49
+
50
+ ### Tool Context Access
51
+
52
+ Without forwarding the execute options, tools are blind to ambient state. Cannot:
53
+
54
+ - Access auth tokens or session info from context
55
+ - Read accumulated state from prior steps
56
+ - Use the `abortSignal` provided by the AI SDK (note: our tools do not receive it, though sub-agent wrappers do)
57
+
58
+ ## Recommended Implementation Order
59
+
60
+ ```
61
+ Phase 1: Foundation (P0)
62
+ ├── Wire prepareStep through AgentConfig + AgentOverrides + agent.ts
63
+ ├── Wire activeTools through AgentConfig + AgentOverrides + agent.ts
64
+ ├── Wire experimental_context through AgentConfig + AgentOverrides + agent.ts
65
+ └── Fix tool() execute to forward AI SDK options (context + abortSignal)
66
+
67
+ Phase 2: Sub-Agent Enhancement (P1)
68
+ ├── Context-aware sub-agent wrapper in buildAITools
69
+ ├── Per-delegation maxSteps support
70
+ └── Result accumulation via prepareStep example/utility
71
+
72
+ Phase 3: Advanced Patterns (P2)
73
+ ├── Dynamic agent spawning via prepareStep
74
+ ├── Shared state store tool
75
+ └── Message compression/summarization utilities
76
+ ```
77
+
78
+ ## Files That Need Changes
79
+
80
+ | File | Changes |
81
+ | ------------------------- | --------------------------------------------------------------------------------- |
82
+ | `src/core/agent/types.ts` | Add `prepareStep`, `activeTools`, `context` to `AgentConfig` and `AgentOverrides` |
83
+ | `src/core/agent/agent.ts` | Forward new fields to `generateText`/`streamText` calls |
84
+ | `src/core/tool.ts` | Extend `ToolConfig.execute` signature; forward AI SDK options in wrapper |
85
+ | `src/core/agent/utils.ts` | Update `buildAITools` sub-agent wrapper to forward context |
86
+ | `src/index.ts` | Export new types |
@@ -0,0 +1,138 @@
1
+ # Research: `prepareStep` and `activeTools` Support
2
+
3
+ > **Date**: 2026-03-06
4
+ > **AI SDK Version**: `^6.0.111`
5
+ > **Status**: Gap identified — neither `prepareStep` nor `activeTools` are wired through the SDK
6
+
7
+ ## Summary
8
+
9
+ The Vercel AI SDK v6 provides two key parameters for controlling the agentic tool-calling loop: `prepareStep` (a callback that fires **before** each step) and `activeTools` (restricts which tools are available). Our agent SDK does not expose or forward either of these to `generateText`/`streamText`.
10
+
11
+ ## What the AI SDK v6 Provides
12
+
13
+ ### `prepareStep`
14
+
15
+ An async callback passed to `generateText`/`streamText` that fires before each step in the tool-calling loop.
16
+
17
+ **Input (`PrepareStepOptions`):**
18
+
19
+ | Field | Type | Description |
20
+ | ---------------------- | --------------------- | ------------------------------------------------------------------------ |
21
+ | `steps` | `StepResult<TOOLS>[]` | All previously executed steps with results, tool calls, and usage |
22
+ | `stepNumber` | `number` | Current step index (0-based) |
23
+ | `model` | `LanguageModel` | The current model |
24
+ | `messages` | `ModelMessage[]` | Messages about to be sent to the model |
25
+ | `experimental_context` | `unknown` | User-defined context (see [context research](./experimental-context.md)) |
26
+
27
+ **Output (`PrepareStepResult<TOOLS>`):**
28
+
29
+ | Field | Type | Description |
30
+ | ---------------------- | --------------------------- | -------------------------------------- |
31
+ | `model` | `LanguageModel` (optional) | Override model for this step |
32
+ | `messages` | `ModelMessage[]` (optional) | Override/transform messages |
33
+ | `activeTools` | `string[]` (optional) | Restrict available tools for this step |
34
+ | `toolChoice` | `ToolChoice` (optional) | Force a specific tool or `"required"` |
35
+ | `system` | `string` (optional) | Override system prompt |
36
+ | `experimental_context` | `unknown` (optional) | Modify context for subsequent steps |
37
+ | `providerOptions` | `Record` (optional) | Provider-specific settings |
38
+
39
+ ### `activeTools`
40
+
41
+ An array of tool name strings that restricts which tools the model can invoke. Available both at the top-level `generateText` call (static restriction) and inside `prepareStep` (dynamic per-step restriction). Does not change the tool call/result types — only limits availability.
42
+
43
+ ## Current SDK State
44
+
45
+ ### `agent.ts` — `generateText` call (lines 237-263)
46
+
47
+ ```typescript
48
+ const aiResult = await generateText({
49
+ model,
50
+ system,
51
+ ...promptParams,
52
+ tools: aiTools,
53
+ output,
54
+ stopWhen: stepCountIs(maxSteps),
55
+ abortSignal: overrideSignal,
56
+ onStepFinish: async (step) => {
57
+ /* observability only */
58
+ },
59
+ });
60
+ ```
61
+
62
+ No `prepareStep` or `activeTools` parameters are passed.
63
+
64
+ ### `AgentConfig` — missing fields
65
+
66
+ The `AgentConfig` interface (`types.ts`) defines hooks for `onStart`, `onFinish`, `onError`, and `onStepFinish`. None of these run **before** a step or can modify the next step's configuration.
67
+
68
+ ### `AgentOverrides` — missing fields
69
+
70
+ The `AgentOverrides` interface has per-call overrides for `model`, `system`, `tools`, `agents`, `maxSteps`, `output`, and hooks. No `prepareStep` or `activeTools`.
71
+
72
+ ## Capabilities Unlocked by Wiring These Through
73
+
74
+ ### Context Window Management
75
+
76
+ ```typescript
77
+ prepareStep: async ({ messages, stepNumber }) => {
78
+ // Keep only last N messages when context grows too large
79
+ if (messages.length > 50) {
80
+ return {
81
+ messages: [messages[0], ...messages.slice(-20)],
82
+ };
83
+ }
84
+ return {};
85
+ };
86
+ ```
87
+
88
+ ### Dynamic Tool Activation
89
+
90
+ ```typescript
91
+ prepareStep: async ({ stepNumber }) => {
92
+ // Phase 1: research only
93
+ if (stepNumber < 3) {
94
+ return { activeTools: ["search", "readFile"] };
95
+ }
96
+ // Phase 2: editing
97
+ return { activeTools: ["editFile", "writeFile"] };
98
+ };
99
+ ```
100
+
101
+ ### Model Switching
102
+
103
+ ```typescript
104
+ prepareStep: async ({ steps }) => {
105
+ const lastStep = steps.at(-1);
106
+ // Switch to a more capable model if the task is complex
107
+ if (lastStep?.toolCalls.length > 3) {
108
+ return { model: openrouter("anthropic/claude-sonnet-4") };
109
+ }
110
+ return {};
111
+ };
112
+ ```
113
+
114
+ ### System Prompt Adaptation
115
+
116
+ ```typescript
117
+ prepareStep: async ({ steps }) => {
118
+ const completedTools = steps.flatMap((s) => s.toolCalls.map((tc) => tc.toolName));
119
+ if (completedTools.includes("analyzeCode")) {
120
+ return { system: "You have analyzed the code. Now focus on generating the fix." };
121
+ }
122
+ return {};
123
+ };
124
+ ```
125
+
126
+ ## Implementation Path
127
+
128
+ 1. Add `prepareStep` and `activeTools` fields to `AgentConfig<TInput, TOutput, TTools, TSubAgents>`
129
+ 2. Add the same fields to `AgentOverrides<TTools, TSubAgents>`
130
+ 3. Forward them to the `generateText`/`streamText` calls in `agent.ts`
131
+ 4. For `prepareStep`, merge base config + per-call override (call-level wraps base-level, or provide a composition utility)
132
+
133
+ ## References
134
+
135
+ - [AI SDK 6 Blog Post](https://vercel.com/blog/ai-sdk-6)
136
+ - [Agents: Loop Control Docs](https://ai-sdk.dev/docs/agents/loop-control)
137
+ - [generateText API Reference](https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-text)
138
+ - [streamText API Reference](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text)