@funkai/agents 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.generated/req.txt +1 -0
- package/.turbo/turbo-build.log +21 -0
- package/.turbo/turbo-test$colon$coverage.log +109 -0
- package/.turbo/turbo-test.log +141 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/CHANGELOG.md +16 -0
- package/ISSUES.md +540 -0
- package/LICENSE +21 -0
- package/README.md +128 -0
- package/banner.svg +97 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/core/agents/base/agent.ts.html +1705 -0
- package/coverage/lcov-report/core/agents/base/index.html +146 -0
- package/coverage/lcov-report/core/agents/base/output.ts.html +256 -0
- package/coverage/lcov-report/core/agents/base/utils.ts.html +694 -0
- package/coverage/lcov-report/core/agents/flow/engine.ts.html +928 -0
- package/coverage/lcov-report/core/agents/flow/flow-agent.ts.html +1462 -0
- package/coverage/lcov-report/core/agents/flow/index.html +146 -0
- package/coverage/lcov-report/core/agents/flow/messages.ts.html +508 -0
- package/coverage/lcov-report/core/agents/flow/steps/factory.ts.html +1975 -0
- package/coverage/lcov-report/core/agents/flow/steps/index.html +116 -0
- package/coverage/lcov-report/core/index.html +131 -0
- package/coverage/lcov-report/core/logger.ts.html +541 -0
- package/coverage/lcov-report/core/models/providers/index.html +116 -0
- package/coverage/lcov-report/core/models/providers/openai.ts.html +337 -0
- package/coverage/lcov-report/core/provider/index.html +131 -0
- package/coverage/lcov-report/core/provider/provider.ts.html +346 -0
- package/coverage/lcov-report/core/provider/usage.ts.html +376 -0
- package/coverage/lcov-report/core/tool.ts.html +577 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +221 -0
- package/coverage/lcov-report/lib/hooks.ts.html +262 -0
- package/coverage/lcov-report/lib/index.html +161 -0
- package/coverage/lcov-report/lib/middleware.ts.html +274 -0
- package/coverage/lcov-report/lib/runnable.ts.html +151 -0
- package/coverage/lcov-report/lib/trace.ts.html +520 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov-report/utils/attempt.ts.html +199 -0
- package/coverage/lcov-report/utils/error.ts.html +421 -0
- package/coverage/lcov-report/utils/index.html +176 -0
- package/coverage/lcov-report/utils/resolve.ts.html +208 -0
- package/coverage/lcov-report/utils/result.ts.html +538 -0
- package/coverage/lcov-report/utils/zod.ts.html +178 -0
- package/coverage/lcov.info +1566 -0
- package/dist/index.d.mts +2883 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2312 -0
- package/dist/index.mjs.map +1 -0
- package/docs/core/agent.md +231 -0
- package/docs/core/hooks.md +95 -0
- package/docs/core/overview.md +87 -0
- package/docs/core/step.md +279 -0
- package/docs/core/tools.md +98 -0
- package/docs/core/workflow.md +235 -0
- package/docs/guides/create-agent.md +224 -0
- package/docs/guides/create-tool.md +137 -0
- package/docs/guides/create-workflow.md +374 -0
- package/docs/overview.md +244 -0
- package/docs/provider/models.md +55 -0
- package/docs/provider/overview.md +106 -0
- package/docs/provider/usage.md +100 -0
- package/docs/research/experimental-context.md +167 -0
- package/docs/research/gap-analysis.md +86 -0
- package/docs/research/prepare-step-and-active-tools.md +138 -0
- package/docs/research/sub-agent-model.md +249 -0
- package/docs/troubleshooting.md +60 -0
- package/logo.svg +17 -0
- package/models.config.json +18 -0
- package/package.json +60 -0
- package/scripts/generate-models.ts +324 -0
- package/src/core/agents/base/agent.test.ts +1522 -0
- package/src/core/agents/base/agent.ts +547 -0
- package/src/core/agents/base/output.test.ts +93 -0
- package/src/core/agents/base/output.ts +57 -0
- package/src/core/agents/base/types.test-d.ts +69 -0
- package/src/core/agents/base/types.ts +503 -0
- package/src/core/agents/base/utils.test.ts +397 -0
- package/src/core/agents/base/utils.ts +197 -0
- package/src/core/agents/flow/engine.test.ts +452 -0
- package/src/core/agents/flow/engine.ts +281 -0
- package/src/core/agents/flow/flow-agent.test.ts +1027 -0
- package/src/core/agents/flow/flow-agent.ts +473 -0
- package/src/core/agents/flow/messages.test.ts +198 -0
- package/src/core/agents/flow/messages.ts +141 -0
- package/src/core/agents/flow/steps/agent.test.ts +280 -0
- package/src/core/agents/flow/steps/agent.ts +87 -0
- package/src/core/agents/flow/steps/all.test.ts +300 -0
- package/src/core/agents/flow/steps/all.ts +73 -0
- package/src/core/agents/flow/steps/builder.ts +124 -0
- package/src/core/agents/flow/steps/each.test.ts +257 -0
- package/src/core/agents/flow/steps/each.ts +61 -0
- package/src/core/agents/flow/steps/factory.test-d.ts +50 -0
- package/src/core/agents/flow/steps/factory.test.ts +1025 -0
- package/src/core/agents/flow/steps/factory.ts +645 -0
- package/src/core/agents/flow/steps/map.test.ts +273 -0
- package/src/core/agents/flow/steps/map.ts +75 -0
- package/src/core/agents/flow/steps/race.test.ts +290 -0
- package/src/core/agents/flow/steps/race.ts +59 -0
- package/src/core/agents/flow/steps/reduce.test.ts +310 -0
- package/src/core/agents/flow/steps/reduce.ts +73 -0
- package/src/core/agents/flow/steps/result.ts +27 -0
- package/src/core/agents/flow/steps/step.test.ts +402 -0
- package/src/core/agents/flow/steps/step.ts +51 -0
- package/src/core/agents/flow/steps/while.test.ts +283 -0
- package/src/core/agents/flow/steps/while.ts +75 -0
- package/src/core/agents/flow/types.ts +348 -0
- package/src/core/logger.test.ts +163 -0
- package/src/core/logger.ts +152 -0
- package/src/core/models/index.test.ts +137 -0
- package/src/core/models/index.ts +152 -0
- package/src/core/models/providers/openai.ts +84 -0
- package/src/core/provider/provider.test.ts +128 -0
- package/src/core/provider/provider.ts +99 -0
- package/src/core/provider/types.ts +98 -0
- package/src/core/provider/usage.test.ts +304 -0
- package/src/core/provider/usage.ts +97 -0
- package/src/core/tool.test.ts +65 -0
- package/src/core/tool.ts +164 -0
- package/src/core/types.ts +66 -0
- package/src/index.ts +95 -0
- package/src/lib/context.test.ts +86 -0
- package/src/lib/context.ts +49 -0
- package/src/lib/hooks.test.ts +102 -0
- package/src/lib/hooks.ts +59 -0
- package/src/lib/middleware.test.ts +122 -0
- package/src/lib/middleware.ts +63 -0
- package/src/lib/runnable.test.ts +41 -0
- package/src/lib/runnable.ts +22 -0
- package/src/lib/trace.test.ts +291 -0
- package/src/lib/trace.ts +145 -0
- package/src/models/index.ts +123 -0
- package/src/models/providers/index.ts +15 -0
- package/src/models/providers/openai.ts +84 -0
- package/src/testing/context.ts +32 -0
- package/src/testing/index.ts +2 -0
- package/src/testing/logger.ts +19 -0
- package/src/utils/attempt.test.ts +127 -0
- package/src/utils/attempt.ts +38 -0
- package/src/utils/error.test.ts +179 -0
- package/src/utils/error.ts +112 -0
- package/src/utils/resolve.test.ts +38 -0
- package/src/utils/resolve.ts +41 -0
- package/src/utils/result.test.ts +79 -0
- package/src/utils/result.ts +151 -0
- package/src/utils/zod.test.ts +69 -0
- package/src/utils/zod.ts +31 -0
- package/tsconfig.json +25 -0
- package/tsdown.config.ts +15 -0
- package/vitest.config.ts +46 -0
|
@@ -0,0 +1,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)
|