@lupaflow/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/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@lupaflow/agent",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "module": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "require": "./dist/index.js",
16
+ "import": "./dist/index.js"
17
+ }
18
+ },
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "dev": "tsup --watch"
22
+ },
23
+ "dependencies": {
24
+ "@lupaflow/core": "workspace:*",
25
+ "@lupaflow/types": "workspace:*",
26
+ "@lupaflow/providers": "workspace:*",
27
+ "@lupaflow/tools": "workspace:*",
28
+ "@lupaflow/memory": "workspace:*",
29
+ "@lupaflow/plugins": "workspace:*"
30
+ },
31
+ "devDependencies": {
32
+ "tsup": "^8.3.0",
33
+ "typescript": "^5.6.0"
34
+ }
35
+ }
package/src/agent.ts ADDED
@@ -0,0 +1,84 @@
1
+ import type { AgentConfig, Tool, Personality, Goal, Constraint } from "@lupaflow/types"
2
+ import { MemoryStore } from "@lupaflow/memory"
3
+ import { AgentRunner, AgentRunOptions, AgentRunResult, AgentStreamChunk } from "./runner"
4
+ import { validateAgentConfig } from "./config"
5
+ import { generateId } from "@lupaflow/core"
6
+ import { toolRegistry } from "@lupaflow/tools"
7
+
8
+ export class Agent {
9
+ public config: AgentConfig
10
+ private memoryStore?: MemoryStore
11
+
12
+ constructor(config: AgentConfig) {
13
+ validateAgentConfig(config)
14
+ this.config = {
15
+ ...config,
16
+ id: config.id || generateId(),
17
+ }
18
+ }
19
+
20
+ tool(t: Tool): this {
21
+ this.config.tools = this.config.tools || []
22
+ this.config.tools.push(t)
23
+ return this
24
+ }
25
+
26
+ useTool(name: string): this {
27
+ try {
28
+ const tool = toolRegistry.get(name)
29
+ this.config.tools = this.config.tools || []
30
+ this.config.tools.push(tool)
31
+ } catch {
32
+ throw new Error(`Tool "${name}" not found in registry`)
33
+ }
34
+ return this
35
+ }
36
+
37
+ memory(memory: MemoryStore): this {
38
+ this.memoryStore = memory
39
+ return this
40
+ }
41
+
42
+ personality(p: Personality): this {
43
+ this.config.personality = p
44
+ return this
45
+ }
46
+
47
+ goal(g: Goal): this {
48
+ this.config.goals = this.config.goals || []
49
+ this.config.goals.push(g)
50
+ return this
51
+ }
52
+
53
+ constraint(c: Constraint): this {
54
+ this.config.constraints = this.config.constraints || []
55
+ this.config.constraints.push(c)
56
+ return this
57
+ }
58
+
59
+ temperature(t: number): this {
60
+ this.config.temperature = t
61
+ return this
62
+ }
63
+
64
+ model(m: string): this {
65
+ this.config.model = m
66
+ return this
67
+ }
68
+
69
+ async run(input: string, options?: Partial<AgentRunOptions>): Promise<AgentRunResult> {
70
+ const runner = new AgentRunner(this.config, this.memoryStore)
71
+ return runner.run({
72
+ input,
73
+ ...options,
74
+ })
75
+ }
76
+
77
+ async *streamRun(input: string, options?: Partial<AgentRunOptions>): AsyncIterableIterator<AgentStreamChunk> {
78
+ const runner = new AgentRunner(this.config, this.memoryStore)
79
+ yield* runner.streamRun({
80
+ input,
81
+ ...options,
82
+ })
83
+ }
84
+ }
package/src/config.ts ADDED
@@ -0,0 +1,51 @@
1
+ import type { AgentConfig } from "@lupaflow/types"
2
+ import { ConfigError } from "@lupaflow/core"
3
+
4
+ export function validateAgentConfig(config: AgentConfig): void {
5
+ if (!config.name) throw new ConfigError("Agent name is required")
6
+ if (!config.provider) throw new ConfigError("Provider is required for agent")
7
+
8
+ if (config.temperature !== undefined && (config.temperature < 0 || config.temperature > 2)) {
9
+ throw new ConfigError("Temperature must be between 0 and 2")
10
+ }
11
+
12
+ if (config.maxRetries !== undefined && config.maxRetries < 0) {
13
+ throw new ConfigError("maxRetries must be >= 0")
14
+ }
15
+ }
16
+
17
+ export function buildSystemPrompt(config: AgentConfig): string {
18
+ const parts: string[] = []
19
+
20
+ parts.push(config.systemPrompt || `You are ${config.name}, an AI assistant.`)
21
+
22
+ if (config.personality) {
23
+ const p = config.personality
24
+ parts.push("")
25
+ parts.push("--- Personality ---")
26
+ if (p.name) parts.push(`Name: ${p.name}`)
27
+ if (p.tone) parts.push(`Tone: ${p.tone}`)
28
+ if (p.style) parts.push(`Style: ${p.style}`)
29
+ if (p.traits?.length) parts.push(`Traits: ${p.traits.join(", ")}`)
30
+ }
31
+
32
+ if (config.goals?.length) {
33
+ parts.push("")
34
+ parts.push("--- Goals ---")
35
+ config.goals.forEach((g, i) => parts.push(`${i + 1}. ${g.description}`))
36
+ }
37
+
38
+ if (config.constraints?.length) {
39
+ parts.push("")
40
+ parts.push("--- Constraints ---")
41
+ config.constraints.forEach((c) => parts.push(`- ${c.description}`))
42
+ }
43
+
44
+ if (config.tools?.length) {
45
+ parts.push("")
46
+ parts.push(`You have access to these tools: ${config.tools.map((t) => t.definition.name).join(", ")}`)
47
+ parts.push("Use them when appropriate to fulfill the user's request.")
48
+ }
49
+
50
+ return parts.join("\n")
51
+ }
package/src/context.ts ADDED
@@ -0,0 +1,63 @@
1
+ import type { Message } from "@lupaflow/types"
2
+ import { estimateTokens } from "@lupaflow/core"
3
+
4
+ export interface ContextConfig {
5
+ maxMessages?: number
6
+ maxTokens?: number
7
+ preserveSystem?: boolean
8
+ }
9
+
10
+ export class ContextManager {
11
+ private messages: Message[] = []
12
+ private config: ContextConfig
13
+
14
+ constructor(config: ContextConfig = {}) {
15
+ this.config = {
16
+ maxMessages: config.maxMessages || 50,
17
+ maxTokens: config.maxTokens || 4000,
18
+ preserveSystem: config.preserveSystem ?? true,
19
+ }
20
+ }
21
+
22
+ add(message: Message): void {
23
+ this.messages.push(message)
24
+ this.trim()
25
+ }
26
+
27
+ getMessages(): Message[] {
28
+ return [...this.messages]
29
+ }
30
+
31
+ getTotalTokens(): number {
32
+ return this.messages.reduce((sum, m) => sum + estimateTokens(m.content || ""), 0)
33
+ }
34
+
35
+ trim(): void {
36
+ if (this.messages.length <= this.config.maxMessages! && this.getTotalTokens() <= this.config.maxTokens!) {
37
+ return
38
+ }
39
+
40
+ while (
41
+ this.messages.length > 2 &&
42
+ (this.messages.length > this.config.maxMessages! || this.getTotalTokens() > this.config.maxTokens!)
43
+ ) {
44
+ const startIdx = this.config.preserveSystem && this.messages[0]?.role === "system" ? 1 : 0
45
+ if (startIdx < this.messages.length) {
46
+ this.messages.splice(startIdx, 1)
47
+ } else {
48
+ break
49
+ }
50
+ }
51
+ }
52
+
53
+ clear(): void {
54
+ const systemMsg = this.config.preserveSystem
55
+ ? this.messages.find((m) => m.role === "system")
56
+ : undefined
57
+ this.messages = systemMsg ? [systemMsg] : []
58
+ }
59
+
60
+ toJSON(): Message[] {
61
+ return this.getMessages()
62
+ }
63
+ }
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ export { Agent } from "./agent"
2
+ export { AgentRunner } from "./runner"
3
+ export type { AgentRunOptions, AgentRunResult, AgentStreamChunk } from "./runner"
4
+ export { ContextManager } from "./context"
5
+ export { withRetry } from "./retry"
6
+ export { buildSystemPrompt, validateAgentConfig } from "./config"
package/src/retry.ts ADDED
@@ -0,0 +1,47 @@
1
+ import { delay } from "@lupaflow/core"
2
+ import { RetryError } from "@lupaflow/core"
3
+ import { globalEventBus } from "@lupaflow/core"
4
+
5
+ export interface RetryConfig {
6
+ maxRetries: number
7
+ baseDelay: number
8
+ maxDelay: number
9
+ backoffFactor: number
10
+ }
11
+
12
+ export async function withRetry<T>(
13
+ fn: () => Promise<T>,
14
+ config: Partial<RetryConfig> = {},
15
+ context?: string
16
+ ): Promise<T> {
17
+ const cfg: RetryConfig = {
18
+ maxRetries: config.maxRetries ?? 3,
19
+ baseDelay: config.baseDelay ?? 1000,
20
+ maxDelay: config.maxDelay ?? 30000,
21
+ backoffFactor: config.backoffFactor ?? 2,
22
+ }
23
+
24
+ let lastError: Error | null = null
25
+
26
+ for (let attempt = 0; attempt <= cfg.maxRetries; attempt++) {
27
+ try {
28
+ return await fn()
29
+ } catch (err: any) {
30
+ lastError = err
31
+ if (attempt < cfg.maxRetries) {
32
+ const waitTime = Math.min(cfg.baseDelay * Math.pow(cfg.backoffFactor, attempt), cfg.maxDelay)
33
+ await globalEventBus.emit("agent:error", {
34
+ error: err.message,
35
+ retryAttempt: attempt + 1,
36
+ } as any)
37
+ await delay(waitTime)
38
+ }
39
+ }
40
+ }
41
+
42
+ throw new RetryError(
43
+ `${context ? `[${context}] ` : ""}Failed after ${cfg.maxRetries + 1} attempts`,
44
+ cfg.maxRetries + 1,
45
+ lastError!
46
+ )
47
+ }
package/src/runner.ts ADDED
@@ -0,0 +1,528 @@
1
+ import type { Message, CompletionResponse, AgentConfig, MemoryEntry, ToolCall } from "@lupaflow/types"
2
+ import { providerRegistry } from "@lupaflow/providers"
3
+ import { ContextManager } from "./context"
4
+ import { MemoryStore } from "@lupaflow/memory"
5
+ import { withRetry } from "./retry"
6
+ import { buildSystemPrompt } from "./config"
7
+ import { generateId } from "@lupaflow/core"
8
+ import { globalEventBus } from "@lupaflow/core"
9
+ import { ToolError } from "@lupaflow/core"
10
+ import {
11
+ runBeforeAgentRunHooks,
12
+ runAfterAgentRunHooks,
13
+ runBeforeToolCallHooks,
14
+ runAfterToolCallHooks,
15
+ runBeforeProviderCallHooks,
16
+ runAfterProviderCallHooks,
17
+ } from "@lupaflow/plugins"
18
+
19
+ export interface AgentRunOptions {
20
+ input: string
21
+ sessionId?: string
22
+ onThinking?: (message: string) => void
23
+ onToolCall?: (tool: string, args: Record<string, unknown>) => void
24
+ onToolResult?: (tool: string, result: unknown) => void
25
+ }
26
+
27
+ export interface AgentRunResult {
28
+ id: string
29
+ output: string
30
+ messages: Message[]
31
+ usage: {
32
+ totalTokens: number
33
+ promptTokens: number
34
+ completionTokens: number
35
+ }
36
+ latencyMs: number
37
+ toolCalls: number
38
+ finishReason: string
39
+ }
40
+
41
+ export type AgentStreamChunk =
42
+ | { type: "text"; text: string }
43
+ | { type: "tool_call"; toolCall: ToolCall }
44
+ | { type: "tool_result"; toolCall: ToolCall; result: unknown }
45
+ | { type: "error"; error: string }
46
+ | {
47
+ type: "finish"
48
+ output: string
49
+ finishReason: string
50
+ usage: { totalTokens: number; promptTokens: number; completionTokens: number }
51
+ toolCalls: number
52
+ latencyMs: number
53
+ }
54
+
55
+ export class AgentRunner {
56
+ private config: AgentConfig
57
+ private context: ContextManager
58
+ private memory?: MemoryStore
59
+ private toolCallCount = 0
60
+ private totalPromptTokens = 0
61
+ private totalCompletionTokens = 0
62
+
63
+ constructor(config: AgentConfig, memory?: MemoryStore) {
64
+ this.config = config
65
+ this.context = new ContextManager({
66
+ maxMessages: config.maxContextMessages || 50,
67
+ })
68
+ this.memory = memory
69
+ }
70
+
71
+ async run(options: AgentRunOptions): Promise<AgentRunResult> {
72
+ const runId = generateId()
73
+ const startTime = Date.now()
74
+ this.toolCallCount = 0
75
+ this.totalPromptTokens = 0
76
+ this.totalCompletionTokens = 0
77
+
78
+ const provider = providerRegistry.get(this.config.provider, {
79
+ model: this.config.model,
80
+ temperature: this.config.temperature,
81
+ maxTokens: this.config.maxTokens,
82
+ topP: this.config.topP,
83
+ })
84
+
85
+ const systemPrompt = buildSystemPrompt(this.config)
86
+
87
+ runBeforeAgentRunHooks({
88
+ input: options.input,
89
+ ...this.config,
90
+ } as any)
91
+
92
+ this.context.add({ role: "system", content: systemPrompt })
93
+ this.context.add({ role: "user", content: options.input })
94
+
95
+ await globalEventBus.emit("agent:start", {
96
+ agentId: this.config.id || this.config.name,
97
+ runId,
98
+ config: this.config as any,
99
+ } as any)
100
+
101
+ await globalEventBus.emit("agent:thinking", {
102
+ agentId: this.config.id || this.config.name,
103
+ runId,
104
+ message: options.input,
105
+ } as any)
106
+
107
+ let finalContent = ""
108
+ let finishReason = "stop"
109
+ let iterationCount = 0
110
+ const maxIterations = 20
111
+
112
+ while (iterationCount < maxIterations) {
113
+ iterationCount++
114
+
115
+ const request = runBeforeProviderCallHooks(this.config.provider, {
116
+ model: this.config.model,
117
+ messages: this.context.getMessages(),
118
+ tools: this.config.tools,
119
+ systemPrompt: undefined,
120
+ temperature: this.config.temperature,
121
+ maxTokens: this.config.maxTokens,
122
+ topP: this.config.topP,
123
+ } as any)
124
+
125
+ const response: CompletionResponse = await withRetry(
126
+ () =>
127
+ provider.complete({
128
+ messages: request.messages as Message[] || this.context.getMessages(),
129
+ tools: this.config.tools,
130
+ systemPrompt: undefined,
131
+ temperature: this.config.temperature,
132
+ maxTokens: this.config.maxTokens,
133
+ topP: this.config.topP,
134
+ model: this.config.model,
135
+ }),
136
+ {
137
+ maxRetries: this.config.maxRetries ?? 3,
138
+ baseDelay: this.config.retryDelay ?? 1000,
139
+ },
140
+ this.config.name
141
+ )
142
+
143
+ runAfterProviderCallHooks(response as any)
144
+
145
+ this.totalPromptTokens += response.usage.promptTokens
146
+ this.totalCompletionTokens += response.usage.completionTokens
147
+
148
+ if (response.toolCalls.length > 0) {
149
+ this.context.add({
150
+ role: "assistant",
151
+ content: response.content || null,
152
+ toolCalls: response.toolCalls.map((tc) => ({
153
+ id: tc.id,
154
+ type: "function" as const,
155
+ function: { name: tc.name, arguments: JSON.stringify(tc.args) },
156
+ })),
157
+ })
158
+ } else if (response.content) {
159
+ this.context.add({ role: "assistant", content: response.content })
160
+ }
161
+
162
+ if (response.toolCalls.length === 0) {
163
+ finalContent = response.content || ""
164
+ finishReason = response.finishReason
165
+ break
166
+ }
167
+
168
+ for (const tc of response.toolCalls) {
169
+ this.toolCallCount++
170
+ await globalEventBus.emit("tool:start", {
171
+ agentId: this.config.id || this.config.name,
172
+ runId,
173
+ tool: tc.name,
174
+ args: tc.args,
175
+ } as any)
176
+
177
+ options.onToolCall?.(tc.name, tc.args)
178
+
179
+ try {
180
+ const tool = this.config.tools?.find((t) => t.definition.name === tc.name)
181
+ if (!tool) throw new ToolError(tc.name, `Tool "${tc.name}" not found on agent`)
182
+
183
+ const hookArgs = runBeforeToolCallHooks(tc.name, tc.args as Record<string, unknown>)
184
+ const result = await tool.execute(hookArgs)
185
+ const hookResult = runAfterToolCallHooks(result)
186
+ const resultStr = typeof hookResult === "string" ? hookResult : JSON.stringify(hookResult, null, 2)
187
+
188
+ this.context.add({
189
+ role: "tool",
190
+ toolCallId: tc.id,
191
+ name: tc.name,
192
+ content: resultStr,
193
+ })
194
+
195
+ await globalEventBus.emit("tool:finish", {
196
+ agentId: this.config.id || this.config.name,
197
+ runId,
198
+ tool: tc.name,
199
+ result: hookResult,
200
+ durationMs: Date.now() - startTime,
201
+ } as any)
202
+
203
+ options.onToolResult?.(tc.name, hookResult)
204
+ } catch (err: any) {
205
+ this.context.add({
206
+ role: "tool",
207
+ toolCallId: tc.id,
208
+ name: tc.name,
209
+ content: `Error: ${err.message}`,
210
+ })
211
+
212
+ await globalEventBus.emit("tool:error", {
213
+ agentId: this.config.id || this.config.name,
214
+ runId,
215
+ tool: tc.name,
216
+ error: err.message,
217
+ } as any)
218
+ }
219
+ }
220
+ }
221
+
222
+ const totalLatency = Date.now() - startTime
223
+
224
+ if (this.memory) {
225
+ await this.memory.store({
226
+ id: generateId(),
227
+ type: "short-term",
228
+ key: `agent:${this.config.name}:input:${generateId()}`,
229
+ content: options.input,
230
+ metadata: { runId, agentName: this.config.name },
231
+ timestamp: Date.now(),
232
+ } as MemoryEntry)
233
+
234
+ if (finalContent) {
235
+ await this.memory.store({
236
+ id: generateId(),
237
+ type: "short-term",
238
+ key: `agent:${this.config.name}:output:${generateId()}`,
239
+ content: finalContent,
240
+ metadata: { runId, agentName: this.config.name },
241
+ timestamp: Date.now(),
242
+ } as MemoryEntry)
243
+ }
244
+ }
245
+
246
+ await globalEventBus.emit("agent:response", {
247
+ agentId: this.config.id || this.config.name,
248
+ runId,
249
+ content: finalContent,
250
+ tokens: this.totalCompletionTokens,
251
+ latencyMs: totalLatency,
252
+ } as any)
253
+
254
+ runAfterAgentRunHooks({
255
+ id: runId,
256
+ output: finalContent,
257
+ messages: this.context.getMessages(),
258
+ usage: {
259
+ totalTokens: this.totalPromptTokens + this.totalCompletionTokens,
260
+ promptTokens: this.totalPromptTokens,
261
+ completionTokens: this.totalCompletionTokens,
262
+ },
263
+ latencyMs: totalLatency,
264
+ toolCalls: this.toolCallCount,
265
+ finishReason,
266
+ })
267
+
268
+ await globalEventBus.emit("agent:complete", {
269
+ agentId: this.config.id || this.config.name,
270
+ runId,
271
+ totalTokens: this.totalPromptTokens + this.totalCompletionTokens,
272
+ totalLatencyMs: totalLatency,
273
+ toolCalls: this.toolCallCount,
274
+ } as any)
275
+
276
+ return {
277
+ id: runId,
278
+ output: finalContent,
279
+ messages: this.context.getMessages(),
280
+ usage: {
281
+ totalTokens: this.totalPromptTokens + this.totalCompletionTokens,
282
+ promptTokens: this.totalPromptTokens,
283
+ completionTokens: this.totalCompletionTokens,
284
+ },
285
+ latencyMs: totalLatency,
286
+ toolCalls: this.toolCallCount,
287
+ finishReason,
288
+ }
289
+ }
290
+
291
+ async *streamRun(options: AgentRunOptions): AsyncIterableIterator<AgentStreamChunk> {
292
+ const runId = generateId()
293
+ const startTime = Date.now()
294
+ this.toolCallCount = 0
295
+ this.totalPromptTokens = 0
296
+ this.totalCompletionTokens = 0
297
+
298
+ const provider = providerRegistry.get(this.config.provider, {
299
+ model: this.config.model,
300
+ temperature: this.config.temperature,
301
+ maxTokens: this.config.maxTokens,
302
+ topP: this.config.topP,
303
+ })
304
+
305
+ const systemPrompt = buildSystemPrompt(this.config)
306
+
307
+ runBeforeAgentRunHooks({
308
+ input: options.input,
309
+ ...this.config,
310
+ } as any)
311
+
312
+ this.context.add({ role: "system", content: systemPrompt })
313
+ this.context.add({ role: "user", content: options.input })
314
+
315
+ await globalEventBus.emit("agent:start", {
316
+ agentId: this.config.id || this.config.name,
317
+ runId,
318
+ config: this.config as any,
319
+ } as any)
320
+
321
+ await globalEventBus.emit("agent:thinking", {
322
+ agentId: this.config.id || this.config.name,
323
+ runId,
324
+ message: options.input,
325
+ } as any)
326
+
327
+ let finalContent = ""
328
+ let finishReason = "stop"
329
+ let iterationCount = 0
330
+ const maxIterations = 20
331
+
332
+ while (iterationCount < maxIterations) {
333
+ iterationCount++
334
+
335
+ const request = runBeforeProviderCallHooks(this.config.provider, {
336
+ model: this.config.model,
337
+ messages: this.context.getMessages(),
338
+ tools: this.config.tools,
339
+ systemPrompt: undefined,
340
+ temperature: this.config.temperature,
341
+ maxTokens: this.config.maxTokens,
342
+ topP: this.config.topP,
343
+ } as any)
344
+
345
+ const messages = (request.messages as Message[]) || this.context.getMessages()
346
+
347
+ // stream from the provider
348
+ const stream = provider.streamComplete({
349
+ messages,
350
+ tools: this.config.tools,
351
+ systemPrompt: undefined,
352
+ temperature: this.config.temperature,
353
+ maxTokens: this.config.maxTokens,
354
+ topP: this.config.topP,
355
+ model: this.config.model,
356
+ })
357
+
358
+ let roundContent = ""
359
+ let roundToolCalls: ToolCall[] = []
360
+
361
+ for await (const chunk of stream) {
362
+ if (chunk.type === "text") {
363
+ roundContent += chunk.text
364
+ yield { type: "text", text: chunk.text }
365
+ } else if (chunk.type === "tool_call") {
366
+ roundToolCalls.push(chunk.toolCall)
367
+ } else if (chunk.type === "finish") {
368
+ roundContent = chunk.content
369
+ roundToolCalls = chunk.toolCalls
370
+ this.totalPromptTokens += chunk.usage.promptTokens
371
+ this.totalCompletionTokens += chunk.usage.completionTokens
372
+ finishReason = chunk.finishReason
373
+ }
374
+ }
375
+
376
+ runAfterProviderCallHooks({
377
+ content: roundContent,
378
+ toolCalls: roundToolCalls,
379
+ } as any)
380
+
381
+ // add assistant message to context
382
+ if (roundToolCalls.length > 0) {
383
+ this.context.add({
384
+ role: "assistant",
385
+ content: roundContent || null,
386
+ toolCalls: roundToolCalls.map((tc) => ({
387
+ id: tc.id,
388
+ type: "function" as const,
389
+ function: { name: tc.name, arguments: JSON.stringify(tc.args) },
390
+ })),
391
+ })
392
+ } else if (roundContent) {
393
+ this.context.add({ role: "assistant", content: roundContent })
394
+ }
395
+
396
+ if (roundToolCalls.length === 0) {
397
+ finalContent = roundContent
398
+ finishReason = finishReason || "stop"
399
+ break
400
+ }
401
+
402
+ // execute tool calls
403
+ for (const tc of roundToolCalls) {
404
+ this.toolCallCount++
405
+ await globalEventBus.emit("tool:start", {
406
+ agentId: this.config.id || this.config.name,
407
+ runId,
408
+ tool: tc.name,
409
+ args: tc.args,
410
+ } as any)
411
+
412
+ options.onToolCall?.(tc.name, tc.args)
413
+ yield { type: "tool_call", toolCall: tc }
414
+
415
+ try {
416
+ const tool = this.config.tools?.find((t) => t.definition.name === tc.name)
417
+ if (!tool) throw new ToolError(tc.name, `Tool "${tc.name}" not found on agent`)
418
+
419
+ const hookArgs = runBeforeToolCallHooks(tc.name, tc.args as Record<string, unknown>)
420
+ const result = await tool.execute(hookArgs)
421
+ const hookResult = runAfterToolCallHooks(result)
422
+ const resultStr = typeof hookResult === "string" ? hookResult : JSON.stringify(hookResult, null, 2)
423
+
424
+ this.context.add({
425
+ role: "tool",
426
+ toolCallId: tc.id,
427
+ name: tc.name,
428
+ content: resultStr,
429
+ })
430
+
431
+ await globalEventBus.emit("tool:finish", {
432
+ agentId: this.config.id || this.config.name,
433
+ runId,
434
+ tool: tc.name,
435
+ result: hookResult,
436
+ durationMs: Date.now() - startTime,
437
+ } as any)
438
+
439
+ options.onToolResult?.(tc.name, hookResult)
440
+ yield { type: "tool_result", toolCall: tc, result: hookResult }
441
+ } catch (err: any) {
442
+ this.context.add({
443
+ role: "tool",
444
+ toolCallId: tc.id,
445
+ name: tc.name,
446
+ content: `Error: ${err.message}`,
447
+ })
448
+
449
+ await globalEventBus.emit("tool:error", {
450
+ agentId: this.config.id || this.config.name,
451
+ runId,
452
+ tool: tc.name,
453
+ error: err.message,
454
+ } as any)
455
+
456
+ yield { type: "error", error: err.message }
457
+ }
458
+ }
459
+ }
460
+
461
+ const totalLatency = Date.now() - startTime
462
+
463
+ if (this.memory) {
464
+ await this.memory.store({
465
+ id: generateId(),
466
+ type: "short-term",
467
+ key: `agent:${this.config.name}:input:${generateId()}`,
468
+ content: options.input,
469
+ metadata: { runId, agentName: this.config.name },
470
+ timestamp: Date.now(),
471
+ } as MemoryEntry)
472
+
473
+ if (finalContent) {
474
+ await this.memory.store({
475
+ id: generateId(),
476
+ type: "short-term",
477
+ key: `agent:${this.config.name}:output:${generateId()}`,
478
+ content: finalContent,
479
+ metadata: { runId, agentName: this.config.name },
480
+ timestamp: Date.now(),
481
+ } as MemoryEntry)
482
+ }
483
+ }
484
+
485
+ await globalEventBus.emit("agent:response", {
486
+ agentId: this.config.id || this.config.name,
487
+ runId,
488
+ content: finalContent,
489
+ tokens: this.totalCompletionTokens,
490
+ latencyMs: totalLatency,
491
+ } as any)
492
+
493
+ runAfterAgentRunHooks({
494
+ id: runId,
495
+ output: finalContent,
496
+ messages: this.context.getMessages(),
497
+ usage: {
498
+ totalTokens: this.totalPromptTokens + this.totalCompletionTokens,
499
+ promptTokens: this.totalPromptTokens,
500
+ completionTokens: this.totalCompletionTokens,
501
+ },
502
+ latencyMs: totalLatency,
503
+ toolCalls: this.toolCallCount,
504
+ finishReason,
505
+ })
506
+
507
+ await globalEventBus.emit("agent:complete", {
508
+ agentId: this.config.id || this.config.name,
509
+ runId,
510
+ totalTokens: this.totalPromptTokens + this.totalCompletionTokens,
511
+ totalLatencyMs: totalLatency,
512
+ toolCalls: this.toolCallCount,
513
+ } as any)
514
+
515
+ yield {
516
+ type: "finish",
517
+ output: finalContent,
518
+ finishReason,
519
+ usage: {
520
+ totalTokens: this.totalPromptTokens + this.totalCompletionTokens,
521
+ promptTokens: this.totalPromptTokens,
522
+ completionTokens: this.totalCompletionTokens,
523
+ },
524
+ toolCalls: this.toolCallCount,
525
+ latencyMs: totalLatency,
526
+ }
527
+ }
528
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src"]
8
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from "tsup"
2
+ export default defineConfig({
3
+ entry: { index: "src/index.ts" },
4
+ format: ["cjs", "esm"],
5
+ dts: true,
6
+ sourcemap: true,
7
+ clean: true,
8
+ external: [/node_modules/],
9
+ })