@lupaflow/agent 0.1.0 → 0.1.2

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