agentsdk-agent 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/LICENSE +21 -0
- package/README.md +290 -0
- package/dist/chunk-55J6XMHW.js +3 -0
- package/dist/chunk-55J6XMHW.js.map +1 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.js +283 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +90 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +58 -0
- package/src/handoff.ts +116 -0
- package/src/index.ts +9 -0
- package/src/memory.ts +62 -0
- package/src/runtime.ts +277 -0
- package/src/types.ts +101 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shetan Mehra
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# @agentsdk/agent
|
|
2
|
+
|
|
3
|
+
> Multi-step agent runtime for the AgentSDK — model + system prompt + tools + a step loop, with **agent-as-tool handoff** as a first-class primitive.
|
|
4
|
+
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
|
|
7
|
+
`@agentsdk/agent` turns an `AgentConfig` (model + system prompt + tools +
|
|
8
|
+
max steps) into a running agent. Each step the model may emit text or tool
|
|
9
|
+
calls; tool calls are executed and their results fed back, until the model
|
|
10
|
+
stops calling tools or `maxSteps` is reached.
|
|
11
|
+
|
|
12
|
+
The standout feature is **agent-as-tool handoff**: an agent can be wrapped
|
|
13
|
+
as a callable tool and handed to another agent. When the parent invokes the
|
|
14
|
+
tool, the child agent runs to completion and its final text is returned as
|
|
15
|
+
the tool result. This enables true multi-agent orchestration — a "router"
|
|
16
|
+
agent can delegate to specialised researcher / writer / critic agents as if
|
|
17
|
+
they were tools.
|
|
18
|
+
|
|
19
|
+
> Built on top of [`@agentsdk/core`](https://github.com/shetanmehra/agentsdk/tree/main/packages/core#readme).
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Table of contents
|
|
24
|
+
|
|
25
|
+
- [Install](#install)
|
|
26
|
+
- [Quick start](#quick-start)
|
|
27
|
+
- [AgentConfig](#agentconfig)
|
|
28
|
+
- [Running an agent](#running-an-agent)
|
|
29
|
+
- [Step events & hooks](#step-events--hooks)
|
|
30
|
+
- [Memory strategies](#memory-strategies)
|
|
31
|
+
- [Agent-as-tool handoff](#agent-as-tool-handoff)
|
|
32
|
+
- [Multi-agent orchestration](#multi-agent-orchestration)
|
|
33
|
+
- [Result shape](#result-shape)
|
|
34
|
+
- [License](#license)
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
bun add @agentsdk/agent @agentsdk/core
|
|
42
|
+
# or
|
|
43
|
+
npm install @agentsdk/agent @agentsdk/core
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Quick start
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import '@agentsdk/core/bootstrap' // auto-register available providers
|
|
52
|
+
import { runAgent, type AgentConfig } from '@agentsdk/agent'
|
|
53
|
+
|
|
54
|
+
const agent: AgentConfig = {
|
|
55
|
+
id: 'writer',
|
|
56
|
+
name: 'Writer',
|
|
57
|
+
model: 'gpt-4o-mini', // or 'glm-4.6', 'claude-3-5-sonnet-20241022', ...
|
|
58
|
+
provider: 'openai', // optional — auto-detected if omitted
|
|
59
|
+
systemPrompt: 'You are a concise technical writer.',
|
|
60
|
+
maxSteps: 5,
|
|
61
|
+
temperature: 0.7,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const result = await runAgent(agent, {
|
|
65
|
+
messages: [{ role: 'user', content: 'Explain async/await in 2 sentences.' }],
|
|
66
|
+
onStepFinish: (event) => console.log(`[step ${event.step}] ${event.kind}`),
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
console.log(result.text)
|
|
70
|
+
console.log(result.steps.length) // number of steps executed
|
|
71
|
+
console.log(result.cost.total) // aggregated USD cost
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
> **Note:** The example uses OpenAI (`gpt-4o-mini`) which works anywhere with
|
|
75
|
+
> `OPENAI_API_KEY` set. To use GLM, set `ZAI_API_KEY`. See [`@agentsdk/core`](https://github.com/shetanmehra/agentsdk/tree/main/packages/core#readme)
|
|
76
|
+
> for full provider configuration.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## AgentConfig
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
interface AgentConfig {
|
|
84
|
+
id: string // stable id within a graph
|
|
85
|
+
name: string // display name
|
|
86
|
+
provider?: string // optional — auto-detected (prefers glm, then openai, then anthropic)
|
|
87
|
+
model: string // e.g. "glm-4.6"
|
|
88
|
+
systemPrompt: string
|
|
89
|
+
tools?: ResolvedTool[] // tools the agent may call
|
|
90
|
+
maxSteps?: number // default 10
|
|
91
|
+
temperature?: number
|
|
92
|
+
maxTokens?: number
|
|
93
|
+
thinking?: boolean // enable provider reasoning mode
|
|
94
|
+
memoryStrategy?: MemoryStrategy // default "full"
|
|
95
|
+
memoryWindow?: number // for "window" strategy, default 8
|
|
96
|
+
providerOptions?: Record<string, unknown>
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
type MemoryStrategy = 'none' | 'full' | 'summary' | 'window'
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Running an agent
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { runAgent, type AgentRunOptions } from '@agentsdk/agent'
|
|
108
|
+
|
|
109
|
+
interface AgentRunOptions {
|
|
110
|
+
messages: Message[] // seed input
|
|
111
|
+
onStepFinish?: StepHook // called after each step
|
|
112
|
+
runId?: string // auto-generated if omitted
|
|
113
|
+
maxSteps?: number // override config.maxSteps for this run
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const result = await runAgent(config, options)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The step loop:
|
|
120
|
+
|
|
121
|
+
1. Seed history with the agent's system prompt + the input messages.
|
|
122
|
+
2. Apply the memory strategy to bound history size.
|
|
123
|
+
3. Call the provider's `generateText`.
|
|
124
|
+
4. If the model emitted tool calls, execute them, append tool results, go to 2.
|
|
125
|
+
5. If the model returned pure text, that's the final answer — stop.
|
|
126
|
+
6. If `maxSteps` is hit mid-tool-call, mark `truncated: true` and synthesise
|
|
127
|
+
a final summary by running one more non-tool generation.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Step events & hooks
|
|
132
|
+
|
|
133
|
+
The `onStepFinish` hook fires after every step (text answer, tool call,
|
|
134
|
+
tool result, or error). Use it for live UI updates, logging, or credit
|
|
135
|
+
deduction per step.
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
interface StepEvent {
|
|
139
|
+
step: number // 1-indexed
|
|
140
|
+
kind: 'text' | 'tool_call' | 'tool_result' | 'error'
|
|
141
|
+
text?: string // assistant text (text/tool_call)
|
|
142
|
+
toolCalls?: ToolCall[] // tool calls requested
|
|
143
|
+
toolResults?: ToolResult[] // tool results produced
|
|
144
|
+
error?: string // when kind === 'error'
|
|
145
|
+
usage: Usage
|
|
146
|
+
cost: Cost
|
|
147
|
+
timestamp: string // ISO
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
type StepHook = (event: StepEvent) => void | Promise<void>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Memory strategies
|
|
156
|
+
|
|
157
|
+
Control how conversation history is carried between steps:
|
|
158
|
+
|
|
159
|
+
| Strategy | Behaviour |
|
|
160
|
+
| ---------- | -------------------------------------------------------------------------- |
|
|
161
|
+
| `none` | Keep only system prompt + last user message |
|
|
162
|
+
| `full` | Keep the entire history (default) |
|
|
163
|
+
| `summary` | Stub — currently behaves like `window`; a real summariser can be plugged in |
|
|
164
|
+
| `window` | Keep system prompt(s) + the last `memoryWindow` messages (default 8) |
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
const agent: AgentConfig = {
|
|
168
|
+
// ...
|
|
169
|
+
memoryStrategy: 'window',
|
|
170
|
+
memoryWindow: 12,
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Agent-as-tool handoff
|
|
177
|
+
|
|
178
|
+
> The core differentiator vs Vercel AI SDK.
|
|
179
|
+
|
|
180
|
+
Wrap any agent as a callable tool, then hand it to a parent agent:
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
import { asTool } from '@agentsdk/agent'
|
|
184
|
+
|
|
185
|
+
const researcher: AgentConfig = {
|
|
186
|
+
id: 'researcher',
|
|
187
|
+
name: 'Researcher',
|
|
188
|
+
model: 'glm-4.6',
|
|
189
|
+
systemPrompt: 'You research topics thoroughly. Always cite sources.',
|
|
190
|
+
maxSteps: 8,
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const researcherTool = asTool(researcher, {
|
|
194
|
+
description: 'Hand off to the Researcher agent for in-depth research.',
|
|
195
|
+
inputKey: 'input', // arg name that becomes the user message
|
|
196
|
+
onChildStep: (event) => { // forwarded child step events
|
|
197
|
+
console.log(` [researcher step ${event.step}]`, event.kind)
|
|
198
|
+
},
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
const parent: AgentConfig = {
|
|
202
|
+
id: 'router',
|
|
203
|
+
name: 'Router',
|
|
204
|
+
model: 'glm-4.6',
|
|
205
|
+
systemPrompt: 'You are a router. Delegate research tasks to the researcher tool.',
|
|
206
|
+
tools: [researcherTool],
|
|
207
|
+
maxSteps: 6,
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
When the parent calls `researcher({ input: '...' })`, the child agent runs to
|
|
212
|
+
completion and the parent receives:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
{
|
|
216
|
+
text: '<child final answer>',
|
|
217
|
+
agentId: 'researcher',
|
|
218
|
+
stepsExecuted: 3,
|
|
219
|
+
usage: { ... },
|
|
220
|
+
cost: { ... },
|
|
221
|
+
truncated: false,
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### `HandoffOptions`
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
interface HandoffOptions {
|
|
229
|
+
description?: string
|
|
230
|
+
parameters?: Record<string, unknown> // JSON Schema; defaults to a single `input` string
|
|
231
|
+
inputKey?: string // default "input"
|
|
232
|
+
seedMessages?: Message[] // extra prepended messages for the child
|
|
233
|
+
onChildStep?: StepHook // live child step events
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Multi-agent orchestration
|
|
240
|
+
|
|
241
|
+
The `withHandoffs` helper composes a parent's tool list from ordinary tools
|
|
242
|
+
plus a set of child agents:
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
import { withHandoffs } from '@agentsdk/agent'
|
|
246
|
+
|
|
247
|
+
const parent: AgentConfig = {
|
|
248
|
+
id: 'orchestrator',
|
|
249
|
+
name: 'Orchestrator',
|
|
250
|
+
model: 'glm-4.6',
|
|
251
|
+
systemPrompt: 'Coordinate the researcher and writer agents.',
|
|
252
|
+
tools: withHandoffs(
|
|
253
|
+
[searchTool, calcTool], // ordinary tools
|
|
254
|
+
[
|
|
255
|
+
{ agent: researcher, opts: { description: 'Research a topic' } },
|
|
256
|
+
{ agent: writer, opts: { description: 'Write a draft' } },
|
|
257
|
+
],
|
|
258
|
+
),
|
|
259
|
+
maxSteps: 12,
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
This pattern scales — one orchestrator can fan out to any number of
|
|
264
|
+
specialised child agents, each with their own model, prompt, and tool set.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Result shape
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
interface AgentRunResult {
|
|
272
|
+
agentId: string
|
|
273
|
+
text: string // final answer
|
|
274
|
+
messages: Message[] // full post-run history
|
|
275
|
+
steps: StepEvent[] // every step, in order
|
|
276
|
+
usage: Usage // aggregated across steps
|
|
277
|
+
cost: Cost // aggregated across steps
|
|
278
|
+
stepsExecuted: number
|
|
279
|
+
truncated: boolean // true if maxSteps hit mid-tool
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## License
|
|
286
|
+
|
|
287
|
+
MIT — see [LICENSE](./LICENSE). Copyright (c) 2026 Shetan Mehra.
|
|
288
|
+
|
|
289
|
+
This package is part of the [AgentSDK](https://github.com/shetanmehra/agentsdk)
|
|
290
|
+
monorepo.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-55J6XMHW.js"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { AgentConfig, AgentRunOptions, AgentRunResult, MemoryStrategy } from './types.js';
|
|
2
|
+
export { StepEvent, StepHook, StepKind } from './types.js';
|
|
3
|
+
import * as agentsdk_core from 'agentsdk-core';
|
|
4
|
+
import { Message, ResolvedTool } from 'agentsdk-core';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* AgentSDK Agent Runtime — Step loop
|
|
8
|
+
* MIT License — see LICENSE. Copyright (c) 2026 Shetan Mehra.
|
|
9
|
+
*
|
|
10
|
+
* The core agent execution loop:
|
|
11
|
+
* 1. Seed history with the agent's system prompt + the input messages.
|
|
12
|
+
* 2. Call the provider's generateText (with automatic retry on transient errors).
|
|
13
|
+
* 3. If the model emitted tool calls, execute them in PARALLEL and append
|
|
14
|
+
* tool results.
|
|
15
|
+
* 4. Otherwise, the agent is done — return the final text.
|
|
16
|
+
* 5. Apply the memory strategy before each step to bound history size.
|
|
17
|
+
* 6. Stop when maxSteps is hit or the model stops calling tools.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
declare function runAgent(config: AgentConfig, options: AgentRunOptions): Promise<AgentRunResult>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* AgentSDK Agent Runtime — Memory strategies
|
|
24
|
+
* MIT License — see LICENSE. Copyright (c) 2026 Shetan Mehra.
|
|
25
|
+
*
|
|
26
|
+
* Reduces the message history between steps according to the agent's
|
|
27
|
+
* `memoryStrategy`. Each function returns the (possibly trimmed) history.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/** Apply the memory strategy to a message history. */
|
|
31
|
+
declare function applyMemory(messages: Message[], strategy: MemoryStrategy, windowSize?: number): Message[];
|
|
32
|
+
|
|
33
|
+
interface HandoffOptions {
|
|
34
|
+
/** Description the parent agent sees for this "tool". */
|
|
35
|
+
description?: string;
|
|
36
|
+
/** Input schema describing what the parent should pass. Defaults to a single `input` string. */
|
|
37
|
+
parameters?: Record<string, unknown>;
|
|
38
|
+
/** The argument key that becomes the user message to the child agent. Default: "input". */
|
|
39
|
+
inputKey?: string;
|
|
40
|
+
/** Extra seed messages to prepend for the child agent (beyond the input). */
|
|
41
|
+
seedMessages?: agentsdk_core.Message[];
|
|
42
|
+
/** Hook fired for every child step (for live UI updates). */
|
|
43
|
+
onChildStep?: AgentRunOptions['onStepFinish'];
|
|
44
|
+
/** Maximum handoff depth before recursion is rejected. Default: 5. */
|
|
45
|
+
maxDepth?: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Wrap an agent as a tool that another agent can call. The resulting tool can
|
|
49
|
+
* be appended to a parent agent's `tools` array.
|
|
50
|
+
*
|
|
51
|
+
* Includes a recursion guard: if the same child agent appears more than
|
|
52
|
+
* `maxDepth` times in the call chain (e.g. A→B→A→B→...), the tool throws
|
|
53
|
+
* instead of looping forever.
|
|
54
|
+
*/
|
|
55
|
+
declare function asTool(child: AgentConfig, opts?: HandoffOptions): ResolvedTool;
|
|
56
|
+
/**
|
|
57
|
+
* Convenience: build a parent agent's tool list by mixing ordinary tools with
|
|
58
|
+
* a set of child agents (each converted to a tool via `asTool`).
|
|
59
|
+
*/
|
|
60
|
+
declare function withHandoffs(baseTools: ResolvedTool[], children: Array<{
|
|
61
|
+
agent: AgentConfig;
|
|
62
|
+
opts?: HandoffOptions;
|
|
63
|
+
}>): ResolvedTool[];
|
|
64
|
+
|
|
65
|
+
export { AgentConfig, AgentRunOptions, AgentRunResult, type HandoffOptions, MemoryStrategy, applyMemory, asTool, runAgent, withHandoffs };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import './chunk-55J6XMHW.js';
|
|
2
|
+
import { addUsage, addCost, normalizeError, generateText, AgentSDKError } from 'agentsdk-core';
|
|
3
|
+
|
|
4
|
+
// src/memory.ts
|
|
5
|
+
function applyMemory(messages, strategy, windowSize = 8) {
|
|
6
|
+
switch (strategy) {
|
|
7
|
+
case "none":
|
|
8
|
+
return keepHeadAndLastUser(messages);
|
|
9
|
+
case "full":
|
|
10
|
+
return messages;
|
|
11
|
+
case "summary":
|
|
12
|
+
warnSummaryDeprecated();
|
|
13
|
+
return applyWindow(messages, windowSize);
|
|
14
|
+
case "window":
|
|
15
|
+
default:
|
|
16
|
+
return applyWindow(messages, windowSize);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function applyWindow(messages, windowSize) {
|
|
20
|
+
const system = [];
|
|
21
|
+
const rest = [];
|
|
22
|
+
for (const m of messages) {
|
|
23
|
+
(m.role === "system" ? system : rest).push(m);
|
|
24
|
+
}
|
|
25
|
+
const tail = rest.slice(-Math.max(1, windowSize));
|
|
26
|
+
return [...system, ...tail];
|
|
27
|
+
}
|
|
28
|
+
function keepHeadAndLastUser(messages) {
|
|
29
|
+
const system = messages.filter((m) => m.role === "system");
|
|
30
|
+
const lastUser = [...messages].reverse().find((m) => m.role === "user");
|
|
31
|
+
return [...system, ...lastUser ? [lastUser] : []];
|
|
32
|
+
}
|
|
33
|
+
var summaryWarned = false;
|
|
34
|
+
function warnSummaryDeprecated() {
|
|
35
|
+
if (summaryWarned) return;
|
|
36
|
+
summaryWarned = true;
|
|
37
|
+
console.warn(
|
|
38
|
+
'[agentsdk:agent] memoryStrategy "summary" is not yet implemented; falling back to "window" behaviour. Use "window" or "full" to silence this warning.'
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/runtime.ts
|
|
43
|
+
var DEFAULT_MAX_STEPS = 10;
|
|
44
|
+
var DEFAULT_MAX_RETRIES = 2;
|
|
45
|
+
var RETRYABLE_TYPES = /* @__PURE__ */ new Set(["rate_limit", "server", "timeout"]);
|
|
46
|
+
var RETRY_BASE_DELAY_MS = 800;
|
|
47
|
+
async function runAgent(config, options) {
|
|
48
|
+
const maxSteps = options.maxSteps ?? config.maxSteps ?? DEFAULT_MAX_STEPS;
|
|
49
|
+
const runId = options.runId ?? makeRunId();
|
|
50
|
+
const onStep = options.onStepFinish;
|
|
51
|
+
let history = [
|
|
52
|
+
{ role: "system", content: config.systemPrompt },
|
|
53
|
+
...options.messages
|
|
54
|
+
];
|
|
55
|
+
const steps = [];
|
|
56
|
+
let totalUsage = {
|
|
57
|
+
promptTokens: 0,
|
|
58
|
+
completionTokens: 0,
|
|
59
|
+
totalTokens: 0
|
|
60
|
+
};
|
|
61
|
+
let totalCost = { input: 0, output: 0, total: 0, currency: "USD" };
|
|
62
|
+
let finalText = "";
|
|
63
|
+
let truncated = false;
|
|
64
|
+
for (let step = 1; step <= maxSteps; step++) {
|
|
65
|
+
const memStrategy = config.memoryStrategy ?? "full";
|
|
66
|
+
const windowSize = config.memoryWindow ?? 8;
|
|
67
|
+
const visible = applyMemory(history, memStrategy, windowSize);
|
|
68
|
+
let event;
|
|
69
|
+
try {
|
|
70
|
+
const result = await generateTextWithRetry(
|
|
71
|
+
{
|
|
72
|
+
model: config.model,
|
|
73
|
+
messages: visible,
|
|
74
|
+
tools: config.tools,
|
|
75
|
+
temperature: config.temperature,
|
|
76
|
+
maxTokens: config.maxTokens,
|
|
77
|
+
thinking: config.thinking,
|
|
78
|
+
providerOptions: config.providerOptions
|
|
79
|
+
},
|
|
80
|
+
{ provider: config.provider }
|
|
81
|
+
);
|
|
82
|
+
totalUsage = addUsage(totalUsage, result.usage);
|
|
83
|
+
totalCost = addCost(totalCost, result.cost);
|
|
84
|
+
const assistantMsg = {
|
|
85
|
+
role: "assistant",
|
|
86
|
+
content: result.text,
|
|
87
|
+
toolCalls: result.toolCalls.length ? result.toolCalls : void 0
|
|
88
|
+
};
|
|
89
|
+
history.push(assistantMsg);
|
|
90
|
+
if (result.toolCalls.length === 0) {
|
|
91
|
+
finalText = result.text;
|
|
92
|
+
event = {
|
|
93
|
+
step,
|
|
94
|
+
kind: "text",
|
|
95
|
+
text: result.text,
|
|
96
|
+
usage: result.usage,
|
|
97
|
+
cost: result.cost,
|
|
98
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
99
|
+
};
|
|
100
|
+
steps.push(event);
|
|
101
|
+
await onStep?.(event);
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
const toolResults = await Promise.all(
|
|
105
|
+
result.toolCalls.map((tc) => executeToolCall(tc, config, step, runId))
|
|
106
|
+
);
|
|
107
|
+
for (const toolResult of toolResults) {
|
|
108
|
+
history.push({
|
|
109
|
+
role: "tool",
|
|
110
|
+
content: stringifyToolResult(toolResult.result),
|
|
111
|
+
name: toolResult.name,
|
|
112
|
+
toolCallId: toolResult.toolCallId
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
event = {
|
|
116
|
+
step,
|
|
117
|
+
kind: "tool_call",
|
|
118
|
+
text: result.text,
|
|
119
|
+
toolCalls: result.toolCalls,
|
|
120
|
+
toolResults,
|
|
121
|
+
usage: result.usage,
|
|
122
|
+
cost: result.cost,
|
|
123
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
124
|
+
};
|
|
125
|
+
steps.push(event);
|
|
126
|
+
await onStep?.(event);
|
|
127
|
+
if (step === maxSteps) truncated = true;
|
|
128
|
+
} catch (err) {
|
|
129
|
+
const norm = normalizeError(err, config.provider);
|
|
130
|
+
event = {
|
|
131
|
+
step,
|
|
132
|
+
kind: "error",
|
|
133
|
+
error: norm.message,
|
|
134
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
135
|
+
};
|
|
136
|
+
steps.push(event);
|
|
137
|
+
await onStep?.(event);
|
|
138
|
+
throw norm;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (!finalText && steps.length > 0) {
|
|
142
|
+
const last = await generateTextWithRetry(
|
|
143
|
+
{
|
|
144
|
+
model: config.model,
|
|
145
|
+
messages: [
|
|
146
|
+
...applyMemory(history, config.memoryStrategy ?? "full", config.memoryWindow ?? 8),
|
|
147
|
+
{
|
|
148
|
+
role: "system",
|
|
149
|
+
content: "Summarise the result of the previous tool calls in a concise final answer for the user."
|
|
150
|
+
}
|
|
151
|
+
],
|
|
152
|
+
temperature: config.temperature,
|
|
153
|
+
maxTokens: config.maxTokens,
|
|
154
|
+
providerOptions: config.providerOptions
|
|
155
|
+
},
|
|
156
|
+
{ provider: config.provider }
|
|
157
|
+
);
|
|
158
|
+
finalText = last.text;
|
|
159
|
+
totalUsage = addUsage(totalUsage, last.usage);
|
|
160
|
+
totalCost = addCost(totalCost, last.cost);
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
agentId: config.id,
|
|
164
|
+
text: finalText,
|
|
165
|
+
messages: history,
|
|
166
|
+
steps,
|
|
167
|
+
usage: totalUsage,
|
|
168
|
+
cost: totalCost,
|
|
169
|
+
stepsExecuted: steps.length,
|
|
170
|
+
truncated
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
async function generateTextWithRetry(params, opts) {
|
|
174
|
+
const maxRetries = DEFAULT_MAX_RETRIES;
|
|
175
|
+
let lastError;
|
|
176
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
177
|
+
try {
|
|
178
|
+
return await generateText(params, opts);
|
|
179
|
+
} catch (err) {
|
|
180
|
+
lastError = err;
|
|
181
|
+
const providerId = opts?.provider;
|
|
182
|
+
const norm = err instanceof AgentSDKError ? err : normalizeError(err, providerId);
|
|
183
|
+
if (attempt === maxRetries || !RETRYABLE_TYPES.has(norm.type)) {
|
|
184
|
+
throw norm;
|
|
185
|
+
}
|
|
186
|
+
const baseDelay = RETRY_BASE_DELAY_MS * Math.pow(2, attempt);
|
|
187
|
+
const jitter = baseDelay * (0.8 + Math.random() * 0.4);
|
|
188
|
+
await new Promise((resolve) => setTimeout(resolve, jitter));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
throw lastError;
|
|
192
|
+
}
|
|
193
|
+
async function executeToolCall(call, config, step, runId) {
|
|
194
|
+
const tool = config.tools?.find((t) => t.name === call.name);
|
|
195
|
+
if (!tool || typeof tool.execute !== "function") {
|
|
196
|
+
return {
|
|
197
|
+
toolCallId: call.id,
|
|
198
|
+
name: call.name,
|
|
199
|
+
result: { error: `Tool "${call.name}" is not defined or has no executor.` },
|
|
200
|
+
isError: true
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
const out = await tool.execute(call.arguments, { runId, step });
|
|
205
|
+
return { toolCallId: call.id, name: call.name, result: out };
|
|
206
|
+
} catch (err) {
|
|
207
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
208
|
+
return {
|
|
209
|
+
toolCallId: call.id,
|
|
210
|
+
name: call.name,
|
|
211
|
+
result: { error: msg },
|
|
212
|
+
isError: true
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function stringifyToolResult(result) {
|
|
217
|
+
if (typeof result === "string") return result;
|
|
218
|
+
try {
|
|
219
|
+
return JSON.stringify(result);
|
|
220
|
+
} catch {
|
|
221
|
+
return String(result);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function makeRunId() {
|
|
225
|
+
return `run_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
226
|
+
}
|
|
227
|
+
function asTool(child, opts = {}) {
|
|
228
|
+
const inputKey = opts.inputKey ?? "input";
|
|
229
|
+
const maxDepth = opts.maxDepth ?? 5;
|
|
230
|
+
return {
|
|
231
|
+
name: child.id,
|
|
232
|
+
description: opts.description ?? `Hand off to the "${child.name}" agent. Pass a natural-language ${inputKey} describing what the child agent should do.`,
|
|
233
|
+
parameters: opts.parameters ?? {
|
|
234
|
+
type: "object",
|
|
235
|
+
properties: {
|
|
236
|
+
[inputKey]: {
|
|
237
|
+
type: "string",
|
|
238
|
+
description: "The task or question to delegate to this agent."
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
required: [inputKey],
|
|
242
|
+
additionalProperties: false
|
|
243
|
+
},
|
|
244
|
+
async execute(args, ctx) {
|
|
245
|
+
const chain = ctx.runId.split(":");
|
|
246
|
+
const occurrences = chain.filter((x) => x === child.id).length;
|
|
247
|
+
if (occurrences > maxDepth) {
|
|
248
|
+
throw new AgentSDKError(
|
|
249
|
+
`Handoff recursion limit exceeded: agent "${child.id}" appears ${occurrences} times in the call chain (max ${maxDepth}). This usually means two agents are handing off to each other in a loop. Check your agent tool lists for cycles.`,
|
|
250
|
+
{ type: "invalid_request" }
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
const input = String(args?.[inputKey] ?? "");
|
|
254
|
+
const messages = [
|
|
255
|
+
...opts.seedMessages ?? [],
|
|
256
|
+
{ role: "user", content: input }
|
|
257
|
+
];
|
|
258
|
+
const result = await runAgent(child, {
|
|
259
|
+
messages,
|
|
260
|
+
runId: `${ctx.runId}:${child.id}`,
|
|
261
|
+
onStepFinish: opts.onChildStep
|
|
262
|
+
});
|
|
263
|
+
return {
|
|
264
|
+
text: result.text,
|
|
265
|
+
agentId: result.agentId,
|
|
266
|
+
stepsExecuted: result.stepsExecuted,
|
|
267
|
+
usage: result.usage,
|
|
268
|
+
cost: result.cost,
|
|
269
|
+
truncated: result.truncated
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
function withHandoffs(baseTools, children) {
|
|
275
|
+
return [
|
|
276
|
+
...baseTools,
|
|
277
|
+
...children.map(({ agent, opts }) => asTool(agent, opts))
|
|
278
|
+
];
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export { applyMemory, asTool, runAgent, withHandoffs };
|
|
282
|
+
//# sourceMappingURL=index.js.map
|
|
283
|
+
//# sourceMappingURL=index.js.map
|