@looopy-ai/core 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/LICENSE +9 -0
  2. package/dist/core/agent.d.ts +53 -0
  3. package/dist/core/agent.js +416 -0
  4. package/dist/core/cleanup.d.ts +12 -0
  5. package/dist/core/cleanup.js +45 -0
  6. package/dist/core/index.d.ts +3 -0
  7. package/dist/core/index.js +3 -0
  8. package/dist/core/iteration.d.ts +5 -0
  9. package/dist/core/iteration.js +60 -0
  10. package/dist/core/logger.d.ts +11 -0
  11. package/dist/core/logger.js +31 -0
  12. package/dist/core/loop.d.ts +5 -0
  13. package/dist/core/loop.js +125 -0
  14. package/dist/core/tools.d.ts +4 -0
  15. package/dist/core/tools.js +78 -0
  16. package/dist/core/types.d.ts +30 -0
  17. package/dist/core/types.js +1 -0
  18. package/dist/events/index.d.ts +3 -0
  19. package/dist/events/index.js +2 -0
  20. package/dist/events/utils.d.ts +250 -0
  21. package/dist/events/utils.js +263 -0
  22. package/dist/index.d.ts +8 -0
  23. package/dist/index.js +8 -0
  24. package/dist/observability/index.d.ts +1 -0
  25. package/dist/observability/index.js +1 -0
  26. package/dist/observability/spans/agent-turn.d.ts +31 -0
  27. package/dist/observability/spans/agent-turn.js +94 -0
  28. package/dist/observability/spans/index.d.ts +5 -0
  29. package/dist/observability/spans/index.js +5 -0
  30. package/dist/observability/spans/iteration.d.ts +14 -0
  31. package/dist/observability/spans/iteration.js +41 -0
  32. package/dist/observability/spans/llm-call.d.ts +14 -0
  33. package/dist/observability/spans/llm-call.js +50 -0
  34. package/dist/observability/spans/loop.d.ts +14 -0
  35. package/dist/observability/spans/loop.js +40 -0
  36. package/dist/observability/spans/tool.d.ts +14 -0
  37. package/dist/observability/spans/tool.js +44 -0
  38. package/dist/observability/tracing.d.ts +58 -0
  39. package/dist/observability/tracing.js +203 -0
  40. package/dist/providers/chat-completions/aggregate.d.ts +4 -0
  41. package/dist/providers/chat-completions/aggregate.js +152 -0
  42. package/dist/providers/chat-completions/content.d.ts +25 -0
  43. package/dist/providers/chat-completions/content.js +229 -0
  44. package/dist/providers/chat-completions/index.d.ts +4 -0
  45. package/dist/providers/chat-completions/index.js +4 -0
  46. package/dist/providers/chat-completions/streaming.d.ts +12 -0
  47. package/dist/providers/chat-completions/streaming.js +3 -0
  48. package/dist/providers/chat-completions/types.d.ts +39 -0
  49. package/dist/providers/chat-completions/types.js +1 -0
  50. package/dist/providers/index.d.ts +2 -0
  51. package/dist/providers/index.js +1 -0
  52. package/dist/providers/litellm-provider.d.ts +43 -0
  53. package/dist/providers/litellm-provider.js +377 -0
  54. package/dist/server/event-buffer.d.ts +37 -0
  55. package/dist/server/event-buffer.js +116 -0
  56. package/dist/server/event-router.d.ts +31 -0
  57. package/dist/server/event-router.js +91 -0
  58. package/dist/server/index.d.ts +3 -0
  59. package/dist/server/index.js +3 -0
  60. package/dist/server/sse.d.ts +60 -0
  61. package/dist/server/sse.js +159 -0
  62. package/dist/stores/artifacts/artifact-scheduler.d.ts +50 -0
  63. package/dist/stores/artifacts/artifact-scheduler.js +86 -0
  64. package/dist/stores/artifacts/index.d.ts +3 -0
  65. package/dist/stores/artifacts/index.js +3 -0
  66. package/dist/stores/artifacts/internal-event-artifact-store.d.ts +54 -0
  67. package/dist/stores/artifacts/internal-event-artifact-store.js +126 -0
  68. package/dist/stores/artifacts/memory-artifact-store.d.ts +52 -0
  69. package/dist/stores/artifacts/memory-artifact-store.js +268 -0
  70. package/dist/stores/filesystem/filesystem-agent-store.d.ts +18 -0
  71. package/dist/stores/filesystem/filesystem-agent-store.js +61 -0
  72. package/dist/stores/filesystem/filesystem-artifact-store.d.ts +59 -0
  73. package/dist/stores/filesystem/filesystem-artifact-store.js +325 -0
  74. package/dist/stores/filesystem/filesystem-context-store.d.ts +37 -0
  75. package/dist/stores/filesystem/filesystem-context-store.js +245 -0
  76. package/dist/stores/filesystem/filesystem-message-store.d.ts +28 -0
  77. package/dist/stores/filesystem/filesystem-message-store.js +149 -0
  78. package/dist/stores/filesystem/filesystem-task-state-store.d.ts +27 -0
  79. package/dist/stores/filesystem/filesystem-task-state-store.js +220 -0
  80. package/dist/stores/filesystem/index.d.ts +10 -0
  81. package/dist/stores/filesystem/index.js +5 -0
  82. package/dist/stores/index.d.ts +5 -0
  83. package/dist/stores/index.js +5 -0
  84. package/dist/stores/memory/memory-state-store.d.ts +15 -0
  85. package/dist/stores/memory/memory-state-store.js +55 -0
  86. package/dist/stores/messages/hybrid-message-store.d.ts +29 -0
  87. package/dist/stores/messages/hybrid-message-store.js +72 -0
  88. package/dist/stores/messages/index.d.ts +4 -0
  89. package/dist/stores/messages/index.js +4 -0
  90. package/dist/stores/messages/interfaces.d.ts +42 -0
  91. package/dist/stores/messages/interfaces.js +18 -0
  92. package/dist/stores/messages/mem0-message-store.d.ts +34 -0
  93. package/dist/stores/messages/mem0-message-store.js +218 -0
  94. package/dist/stores/messages/memory-message-store.d.ts +27 -0
  95. package/dist/stores/messages/memory-message-store.js +183 -0
  96. package/dist/tools/artifact-tools.d.ts +4 -0
  97. package/dist/tools/artifact-tools.js +277 -0
  98. package/dist/tools/client-tool-provider.d.ts +25 -0
  99. package/dist/tools/client-tool-provider.js +139 -0
  100. package/dist/tools/index.d.ts +4 -0
  101. package/dist/tools/index.js +4 -0
  102. package/dist/tools/local-tools.d.ts +13 -0
  103. package/dist/tools/local-tools.js +70 -0
  104. package/dist/tools/mcp-client.d.ts +29 -0
  105. package/dist/tools/mcp-client.js +62 -0
  106. package/dist/tools/mcp-tool-provider.d.ts +22 -0
  107. package/dist/tools/mcp-tool-provider.js +86 -0
  108. package/dist/types/a2a.d.ts +36 -0
  109. package/dist/types/a2a.js +1 -0
  110. package/dist/types/agent.d.ts +14 -0
  111. package/dist/types/agent.js +1 -0
  112. package/dist/types/artifact.d.ts +126 -0
  113. package/dist/types/artifact.js +1 -0
  114. package/dist/types/context.d.ts +13 -0
  115. package/dist/types/context.js +1 -0
  116. package/dist/types/event.d.ts +360 -0
  117. package/dist/types/event.js +30 -0
  118. package/dist/types/index.d.ts +9 -0
  119. package/dist/types/index.js +9 -0
  120. package/dist/types/llm.d.ts +24 -0
  121. package/dist/types/llm.js +1 -0
  122. package/dist/types/message.d.ts +9 -0
  123. package/dist/types/message.js +1 -0
  124. package/dist/types/state.d.ts +86 -0
  125. package/dist/types/state.js +1 -0
  126. package/dist/types/tools.d.ts +57 -0
  127. package/dist/types/tools.js +53 -0
  128. package/dist/utils/error.d.ts +8 -0
  129. package/dist/utils/error.js +23 -0
  130. package/dist/utils/process-signals.d.ts +3 -0
  131. package/dist/utils/process-signals.js +67 -0
  132. package/package.json +54 -0
package/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Greg Bacchus
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,53 @@
1
+ import { Observable } from 'rxjs';
2
+ import type { MessageStore } from '../stores/messages/interfaces';
3
+ import type { AgentState, AgentStore } from '../types/agent';
4
+ import type { AuthContext } from '../types/context';
5
+ import type { AnyEvent } from '../types/event';
6
+ import type { LLMProvider } from '../types/llm';
7
+ import type { Message } from '../types/message';
8
+ import type { ToolProvider } from '../types/tools';
9
+ export interface AgentConfig {
10
+ agentId: string;
11
+ contextId: string;
12
+ llmProvider: LLMProvider;
13
+ toolProviders: ToolProvider[];
14
+ messageStore: MessageStore;
15
+ agentStore?: AgentStore;
16
+ autoCompact?: boolean;
17
+ maxMessages?: number;
18
+ systemPrompt?: string;
19
+ logger?: import('pino').Logger;
20
+ }
21
+ export interface GetMessagesOptions {
22
+ maxMessages?: number;
23
+ maxTokens?: number;
24
+ }
25
+ export declare class Agent {
26
+ private readonly config;
27
+ private _state;
28
+ private logger;
29
+ private sigtermHandler?;
30
+ private sigintHandler?;
31
+ private shuttingDown;
32
+ private shutdownComplete;
33
+ constructor(config: AgentConfig);
34
+ get state(): Readonly<AgentState>;
35
+ get agentId(): string;
36
+ get contextId(): string;
37
+ private initialize;
38
+ startTurn(userMessage: string | null, options?: {
39
+ authContext?: AuthContext;
40
+ taskId?: string;
41
+ }): Promise<Observable<AnyEvent>>;
42
+ private executeInternal;
43
+ shutdown(): Promise<void>;
44
+ getMessages(options?: GetMessagesOptions): Promise<Message[]>;
45
+ clear(): Promise<void>;
46
+ private persistState;
47
+ private persistStateSafely;
48
+ private loadPersistedState;
49
+ private loadMessages;
50
+ private checkAndCompact;
51
+ private registerSignalHandlers;
52
+ private removeSignalHandlers;
53
+ }
@@ -0,0 +1,416 @@
1
+ import { catchError, concat, Observable, of, tap } from 'rxjs';
2
+ import { createTaskStatusEvent } from '../events';
3
+ import { addMessagesCompactedEvent, addMessagesLoadedEvent, completeAgentInitializeSpan, completeAgentTurnSpan, failAgentInitializeSpan, failAgentTurnSpan, setResumeAttributes, setTurnCountAttribute, startAgentInitializeSpan, startAgentTurnSpan, } from '../observability/spans';
4
+ import { serializeError } from '../utils/error';
5
+ import { registerSignalListener, unregisterSignalListener } from '../utils/process-signals';
6
+ import { getLogger } from './logger';
7
+ import { runLoop } from './loop';
8
+ export class Agent {
9
+ config;
10
+ _state;
11
+ logger;
12
+ sigtermHandler;
13
+ sigintHandler;
14
+ shuttingDown = false;
15
+ shutdownComplete = false;
16
+ constructor(config) {
17
+ this.config = {
18
+ autoCompact: false,
19
+ maxMessages: 100,
20
+ systemPrompt: 'You are a helpful AI assistant.',
21
+ ...config,
22
+ logger: config.logger?.child({ contextId: config.contextId }) ||
23
+ getLogger({ contextId: config.contextId }),
24
+ };
25
+ this.logger = this.config.logger.child({ component: 'agent' });
26
+ this._state = {
27
+ status: 'created',
28
+ turnCount: 0,
29
+ lastActivity: new Date(),
30
+ createdAt: new Date(),
31
+ };
32
+ this.logger.debug('Agent created');
33
+ this.persistStateSafely();
34
+ this.registerSignalHandlers();
35
+ }
36
+ get state() {
37
+ return { ...this._state };
38
+ }
39
+ get agentId() {
40
+ return this.config.agentId;
41
+ }
42
+ get contextId() {
43
+ return this.config.contextId;
44
+ }
45
+ async initialize(parentContext) {
46
+ if (this._state.status !== 'created') {
47
+ return;
48
+ }
49
+ const { span } = startAgentInitializeSpan({
50
+ agentId: this.config.agentId,
51
+ contextId: this.config.contextId,
52
+ parentContext,
53
+ });
54
+ try {
55
+ this.logger.info('Initializing agent');
56
+ const hasStoredState = await this.loadPersistedState();
57
+ const existingMessages = await this.config.messageStore.getAll(this.config.contextId);
58
+ if (existingMessages.length > 0) {
59
+ this.logger.info({ messageCount: existingMessages.length }, 'Resuming agent with existing message history');
60
+ if (!hasStoredState) {
61
+ this._state.turnCount = Math.floor(existingMessages.length / 2);
62
+ }
63
+ setResumeAttributes(span, existingMessages.length);
64
+ }
65
+ this._state.status = 'ready';
66
+ this._state.lastActivity = new Date();
67
+ await this.persistState();
68
+ this.logger.info({ status: this._state.status }, 'Agent initialized');
69
+ completeAgentInitializeSpan(span);
70
+ }
71
+ catch (error) {
72
+ this._state.status = 'error';
73
+ this._state.error = serializeError(error);
74
+ this.logger.error({ error: serializeError(error) }, 'Failed to initialize agent');
75
+ await this.persistState();
76
+ failAgentInitializeSpan(span, error);
77
+ throw error;
78
+ }
79
+ }
80
+ async startTurn(userMessage, options) {
81
+ const turnNumber = this._state.turnCount + 1;
82
+ const taskId = options?.taskId || `${this.config.contextId}-turn-${turnNumber}-${Date.now()}`;
83
+ const logger = this.logger.child({ taskId, turnNumber });
84
+ const { span: rootSpan, traceContext: rootContext, tapFinish, } = startAgentTurnSpan({
85
+ agentId: this.config.agentId,
86
+ taskId,
87
+ contextId: this.config.contextId,
88
+ turnNumber,
89
+ userMessage,
90
+ });
91
+ logger.trace({ userMessage, spanName: `agent.turn[${this.config.agentId}]` }, 'Created agent turn span with input');
92
+ try {
93
+ if (this._state.status === 'created') {
94
+ await this.initialize(rootContext);
95
+ }
96
+ if (this._state.status === 'shutdown') {
97
+ const error = new Error('Cannot execute turn: Agent has been shutdown');
98
+ logger.error(error.message);
99
+ failAgentTurnSpan(rootSpan, error);
100
+ return of(createTaskStatusEvent({
101
+ contextId: this.config.contextId,
102
+ taskId,
103
+ status: 'failed',
104
+ message: error.message,
105
+ metadata: { error: error.message },
106
+ }));
107
+ }
108
+ if (this._state.status === 'error') {
109
+ const error = new Error(`Cannot execute turn: Agent is in error state: ${this._state.error?.message}`);
110
+ logger.error(error.message);
111
+ failAgentTurnSpan(rootSpan, error);
112
+ return of(createTaskStatusEvent({
113
+ contextId: this.config.contextId,
114
+ taskId,
115
+ status: 'failed',
116
+ message: error.message,
117
+ metadata: { error: error.message },
118
+ }));
119
+ }
120
+ if (this._state.status === 'busy') {
121
+ const error = new Error('Cannot execute turn: Agent is already executing a turn');
122
+ logger.error(error.message);
123
+ failAgentTurnSpan(rootSpan, error);
124
+ return of(createTaskStatusEvent({
125
+ contextId: this.config.contextId,
126
+ taskId,
127
+ status: 'failed',
128
+ message: error.message,
129
+ metadata: { error: error.message },
130
+ }));
131
+ }
132
+ logger.info({ userMessage }, 'Starting turn');
133
+ this._state.status = 'busy';
134
+ this._state.lastActivity = new Date();
135
+ await this.persistState();
136
+ return this.executeInternal(userMessage, taskId, options?.authContext, rootSpan, rootContext, this.logger.child({ taskId, turnNumber })).pipe(tapFinish);
137
+ }
138
+ catch (error) {
139
+ const err = error;
140
+ logger.error({ error: err }, 'Failed to start turn');
141
+ failAgentTurnSpan(rootSpan, err);
142
+ return of(createTaskStatusEvent({
143
+ contextId: this.config.contextId,
144
+ taskId,
145
+ status: 'failed',
146
+ message: err.message,
147
+ metadata: { error: err.message },
148
+ }));
149
+ }
150
+ }
151
+ executeInternal(userMessage, taskId, authContext, turnSpan, turnContext, logger) {
152
+ const turnNumber = this._state.turnCount + 1;
153
+ return concat(new Observable((observer) => {
154
+ const execute = async () => {
155
+ try {
156
+ const messages = await this.loadMessages();
157
+ addMessagesLoadedEvent(turnSpan, messages.length);
158
+ if (userMessage) {
159
+ messages.push({
160
+ role: 'user',
161
+ content: userMessage,
162
+ });
163
+ await this.config.messageStore.append(this.config.contextId, [
164
+ { role: 'user', content: userMessage },
165
+ ]);
166
+ }
167
+ logger.debug({
168
+ messageCount: messages.length,
169
+ }, 'Loaded messages for turn');
170
+ const turnEvents$ = runLoop({
171
+ agentId: this.config.agentId,
172
+ contextId: this.config.contextId,
173
+ taskId,
174
+ authContext,
175
+ parentContext: turnContext,
176
+ toolProviders: this.config.toolProviders,
177
+ logger: this.config.logger.child({ taskId, turnNumber }),
178
+ turnNumber,
179
+ }, {
180
+ llmProvider: this.config.llmProvider,
181
+ maxIterations: 5,
182
+ stopOnToolError: false,
183
+ }, messages).pipe(tap(async (event) => {
184
+ switch (event.kind) {
185
+ case 'content-complete':
186
+ if (event.content || event.toolCalls) {
187
+ await this.config.messageStore.append(this.config.contextId, [
188
+ {
189
+ role: 'assistant',
190
+ content: event.content,
191
+ toolCalls: event.toolCalls?.map((toolCall) => ({
192
+ id: toolCall.id,
193
+ type: toolCall.type,
194
+ function: {
195
+ name: toolCall.function.name,
196
+ arguments: toolCall.function.arguments || {},
197
+ },
198
+ })),
199
+ },
200
+ ]);
201
+ }
202
+ break;
203
+ case 'tool-complete': {
204
+ const message = {
205
+ role: 'tool',
206
+ content: JSON.stringify({
207
+ success: event.success,
208
+ result: event.result,
209
+ error: event.error,
210
+ }),
211
+ toolCallId: event.toolCallId,
212
+ };
213
+ logger.debug({ message }, 'Saving tool message to message store');
214
+ await this.config.messageStore.append(this.config.contextId, [message]);
215
+ break;
216
+ }
217
+ default:
218
+ break;
219
+ }
220
+ }));
221
+ turnEvents$.subscribe({
222
+ next: (event) => {
223
+ observer.next(event);
224
+ },
225
+ error: (error) => {
226
+ logger.error({ error }, 'Turn execution failed');
227
+ failAgentTurnSpan(turnSpan, error);
228
+ observer.error(error);
229
+ },
230
+ complete: async () => {
231
+ try {
232
+ this._state.turnCount++;
233
+ this._state.lastActivity = new Date();
234
+ this._state.status = 'ready';
235
+ await this.persistState();
236
+ setTurnCountAttribute(turnSpan, this._state.turnCount);
237
+ if (this.config.autoCompact) {
238
+ await this.checkAndCompact();
239
+ addMessagesCompactedEvent(turnSpan);
240
+ }
241
+ logger.info({ turnCount: this._state.turnCount }, 'Turn completed');
242
+ completeAgentTurnSpan(turnSpan);
243
+ observer.complete();
244
+ }
245
+ catch (error) {
246
+ logger.error({ error }, 'Failed to save turn results');
247
+ failAgentTurnSpan(turnSpan, error);
248
+ observer.error(error);
249
+ }
250
+ },
251
+ });
252
+ }
253
+ catch (error) {
254
+ logger.error({ error }, 'Failed to prepare turn');
255
+ failAgentTurnSpan(turnSpan, error);
256
+ observer.error(error);
257
+ }
258
+ };
259
+ execute();
260
+ }).pipe(catchError((error) => {
261
+ this._state.status = 'error';
262
+ this._state.error = error;
263
+ this.persistStateSafely();
264
+ failAgentTurnSpan(turnSpan, error);
265
+ return of(createTaskStatusEvent({
266
+ contextId: this.config.contextId,
267
+ taskId,
268
+ status: 'failed',
269
+ message: error.message,
270
+ metadata: { error: error.message },
271
+ }));
272
+ })));
273
+ }
274
+ async shutdown() {
275
+ if (this.shutdownComplete) {
276
+ this.logger.debug('Agent already shutdown');
277
+ return;
278
+ }
279
+ if (this.shuttingDown) {
280
+ this.logger.debug('Shutdown already in progress');
281
+ return;
282
+ }
283
+ this.shuttingDown = true;
284
+ this.logger.info('Shutting down agent');
285
+ try {
286
+ this._state.status = 'shutdown';
287
+ this._state.lastActivity = new Date();
288
+ await this.persistState();
289
+ this.removeSignalHandlers();
290
+ this.shutdownComplete = true;
291
+ this.config.logger.info('Agent shutdown complete');
292
+ }
293
+ catch (error) {
294
+ this.config.logger.error({ error }, 'Failed to shutdown agent');
295
+ throw error;
296
+ }
297
+ finally {
298
+ this.shuttingDown = false;
299
+ }
300
+ }
301
+ async getMessages(options = {}) {
302
+ if (options.maxMessages || options.maxTokens) {
303
+ return this.config.messageStore.getRecent(this.config.contextId, options);
304
+ }
305
+ return this.config.messageStore.getAll(this.config.contextId);
306
+ }
307
+ async clear() {
308
+ this.logger.info('Clearing agent data');
309
+ try {
310
+ await this.config.messageStore.clear(this.config.contextId);
311
+ this._state.turnCount = 0;
312
+ this._state.lastActivity = new Date();
313
+ await this.persistState();
314
+ this.logger.info('Agent data cleared');
315
+ }
316
+ catch (error) {
317
+ this.logger.error({ error }, 'Failed to clear agent');
318
+ throw error;
319
+ }
320
+ }
321
+ async persistState() {
322
+ if (!this.config.agentStore) {
323
+ return;
324
+ }
325
+ const stateToPersist = {
326
+ ...this._state,
327
+ lastActivity: new Date(this._state.lastActivity),
328
+ createdAt: new Date(this._state.createdAt),
329
+ };
330
+ await this.config.agentStore.save(this.config.contextId, stateToPersist);
331
+ }
332
+ persistStateSafely() {
333
+ if (!this.config.agentStore) {
334
+ return;
335
+ }
336
+ void this.persistState().catch((error) => {
337
+ this.logger.error({ error }, 'Failed to persist agent state');
338
+ });
339
+ }
340
+ async loadPersistedState() {
341
+ if (!this.config.agentStore) {
342
+ return false;
343
+ }
344
+ const persistedState = await this.config.agentStore.load(this.config.contextId);
345
+ if (!persistedState) {
346
+ return false;
347
+ }
348
+ this._state = {
349
+ ...persistedState,
350
+ lastActivity: new Date(persistedState.lastActivity),
351
+ createdAt: new Date(persistedState.createdAt),
352
+ };
353
+ this.logger.debug({ turnCount: this._state.turnCount, status: this._state.status }, 'Loaded agent state from agent store');
354
+ return true;
355
+ }
356
+ async loadMessages() {
357
+ return this.config.messageStore.getRecent(this.config.contextId, {
358
+ maxMessages: this.config.maxMessages,
359
+ });
360
+ }
361
+ async checkAndCompact() {
362
+ const allMessages = await this.config.messageStore.getAll(this.config.contextId);
363
+ if (allMessages.length > this.config.maxMessages) {
364
+ this.logger.info({
365
+ messageCount: allMessages.length,
366
+ maxMessages: this.config.maxMessages,
367
+ }, 'Auto-compacting message history');
368
+ await this.config.messageStore.compact(this.config.contextId, {
369
+ strategy: 'summarization',
370
+ keepRecent: Math.floor(this.config.maxMessages * 0.5),
371
+ });
372
+ }
373
+ }
374
+ registerSignalHandlers() {
375
+ if (typeof process === 'undefined') {
376
+ return;
377
+ }
378
+ if (this.sigtermHandler || typeof process === 'undefined') {
379
+ return;
380
+ }
381
+ if (!this.sigtermHandler) {
382
+ this.sigtermHandler = async () => {
383
+ this.logger.info('Received SIGTERM signal. Initiating graceful shutdown.');
384
+ try {
385
+ await this.shutdown();
386
+ }
387
+ catch (error) {
388
+ this.logger.error({ error }, 'Failed to shutdown agent after SIGTERM signal');
389
+ }
390
+ };
391
+ registerSignalListener('SIGTERM', this.sigtermHandler);
392
+ }
393
+ if (!this.sigintHandler) {
394
+ const sigintHandler = async () => {
395
+ this.logger.info('Received SIGINT signal. Initiating graceful shutdown.');
396
+ try {
397
+ await this.shutdown();
398
+ }
399
+ catch (error) {
400
+ this.logger.error({ error }, 'Failed to shutdown agent after SIGINT signal');
401
+ }
402
+ };
403
+ registerSignalListener('SIGINT', sigintHandler);
404
+ }
405
+ }
406
+ removeSignalHandlers() {
407
+ if (this.sigtermHandler) {
408
+ unregisterSignalListener('SIGTERM', this.sigtermHandler);
409
+ this.sigtermHandler = undefined;
410
+ }
411
+ if (this.sigintHandler) {
412
+ unregisterSignalListener('SIGINT', this.sigintHandler);
413
+ this.sigintHandler = undefined;
414
+ }
415
+ }
416
+ }
@@ -0,0 +1,12 @@
1
+ import type { ArtifactStore } from '../types/artifact';
2
+ import type { TaskStateStore } from '../types/state';
3
+ export declare class StateCleanupService {
4
+ private taskStateStore;
5
+ private artifactStore;
6
+ private intervalMs;
7
+ private intervalHandle?;
8
+ constructor(taskStateStore: TaskStateStore, artifactStore: ArtifactStore, intervalMs?: number);
9
+ start(): void;
10
+ stop(): void;
11
+ cleanupExpiredTasks(): Promise<void>;
12
+ }
@@ -0,0 +1,45 @@
1
+ export class StateCleanupService {
2
+ taskStateStore;
3
+ artifactStore;
4
+ intervalMs;
5
+ intervalHandle;
6
+ constructor(taskStateStore, artifactStore, intervalMs = 60 * 60 * 1000) {
7
+ this.taskStateStore = taskStateStore;
8
+ this.artifactStore = artifactStore;
9
+ this.intervalMs = intervalMs;
10
+ }
11
+ start() {
12
+ if (this.intervalHandle) {
13
+ throw new Error('Cleanup service already started');
14
+ }
15
+ this.intervalHandle = setInterval(() => this.cleanupExpiredTasks(), this.intervalMs);
16
+ }
17
+ stop() {
18
+ if (this.intervalHandle) {
19
+ clearInterval(this.intervalHandle);
20
+ this.intervalHandle = undefined;
21
+ }
22
+ }
23
+ async cleanupExpiredTasks() {
24
+ const cutoffDate = new Date(Date.now() - 24 * 60 * 60 * 1000);
25
+ const taskIds = await this.taskStateStore.listTasks({
26
+ completedAfter: cutoffDate,
27
+ });
28
+ for (const taskId of taskIds) {
29
+ try {
30
+ const state = await this.taskStateStore.load(taskId);
31
+ if (!state)
32
+ continue;
33
+ if (state.artifactIds && state.contextId) {
34
+ for (const artifactId of state.artifactIds) {
35
+ await this.artifactStore.deleteArtifact(state.contextId, artifactId);
36
+ }
37
+ }
38
+ await this.taskStateStore.delete(taskId);
39
+ }
40
+ catch (error) {
41
+ console.error(`Failed to cleanup task ${taskId}:`, error);
42
+ }
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,3 @@
1
+ export { Agent, type AgentConfig, type GetMessagesOptions } from './agent';
2
+ export { StateCleanupService } from './cleanup';
3
+ export { createLogger, getLogger, type LoggerConfig, type LogLevel, setDefaultLogger, } from './logger';
@@ -0,0 +1,3 @@
1
+ export { Agent } from './agent';
2
+ export { StateCleanupService } from './cleanup';
3
+ export { createLogger, getLogger, setDefaultLogger, } from './logger';
@@ -0,0 +1,5 @@
1
+ import { type Observable } from 'rxjs';
2
+ import type { AnyEvent } from '../types/event';
3
+ import type { Message } from '../types/message';
4
+ import type { IterationConfig, LoopContext } from './types';
5
+ export declare const runIteration: (context: LoopContext, config: IterationConfig, history: Message[]) => Observable<AnyEvent>;
@@ -0,0 +1,60 @@
1
+ import { concat, defer, filter, map, mergeMap, shareReplay } from 'rxjs';
2
+ import { startLLMCallSpan, startLoopIterationSpan } from '../observability/spans';
3
+ import { runToolCall } from './tools';
4
+ export const runIteration = (context, config, history) => {
5
+ const logger = context.logger.child({
6
+ component: 'iteration',
7
+ iteration: config.iterationNumber,
8
+ });
9
+ const { traceContext: iterationContext, tapFinish: finishIterationSpan } = startLoopIterationSpan({ ...context, logger }, config.iterationNumber);
10
+ const llmEvents$ = defer(async () => {
11
+ const messages = prepareMessages(context, history);
12
+ const tools = await prepareTools(context.toolProviders);
13
+ return { messages, tools };
14
+ }).pipe(mergeMap(({ messages, tools }) => {
15
+ const { tapFinish: finishLLMCallSpan } = startLLMCallSpan({ ...context, parentContext: iterationContext }, messages);
16
+ return config.llmProvider
17
+ .call({
18
+ messages,
19
+ tools,
20
+ stream: true,
21
+ sessionId: context.taskId,
22
+ })
23
+ .pipe(map((event) => ({
24
+ contextId: context.contextId,
25
+ taskId: context.taskId,
26
+ ...event,
27
+ })), finishLLMCallSpan);
28
+ }), shareReplay());
29
+ const toolEvents$ = llmEvents$.pipe(filter((event) => event.kind === 'tool-call'), mergeMap((event) => runToolCall({
30
+ ...context,
31
+ logger: context.logger.child({ iteration: config.iterationNumber }),
32
+ parentContext: iterationContext,
33
+ }, event)));
34
+ return concat(llmEvents$, toolEvents$).pipe(finishIterationSpan);
35
+ };
36
+ const prepareMessages = (context, history) => {
37
+ const messages = [];
38
+ if (context.systemPrompt) {
39
+ messages.push({
40
+ role: 'system',
41
+ content: context.systemPrompt,
42
+ name: 'system-prompt',
43
+ });
44
+ }
45
+ if (context.skillPrompts) {
46
+ for (const [name, content] of Object.entries(context.skillPrompts)) {
47
+ messages.push({
48
+ role: 'system',
49
+ content,
50
+ name,
51
+ });
52
+ }
53
+ }
54
+ return messages.concat(history);
55
+ };
56
+ const prepareTools = async (toolProviders) => {
57
+ const toolPromises = toolProviders.map((p) => p.getTools());
58
+ const toolArrays = await Promise.all(toolPromises);
59
+ return toolArrays.flat();
60
+ };
@@ -0,0 +1,11 @@
1
+ import pino from 'pino';
2
+ export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
3
+ export interface LoggerConfig {
4
+ level?: LogLevel;
5
+ pretty?: boolean;
6
+ context?: Record<string, unknown>;
7
+ }
8
+ export declare function createLogger(config?: LoggerConfig): pino.Logger;
9
+ export declare let DEFAULT_LOGGER: pino.Logger<never, boolean>;
10
+ export declare function setDefaultLogger(logger: pino.Logger): void;
11
+ export declare function getLogger(context: Record<string, unknown>): pino.Logger;
@@ -0,0 +1,31 @@
1
+ import pino from 'pino';
2
+ export function createLogger(config = {}) {
3
+ const { level = 'info', pretty = false, context = {} } = config;
4
+ const pinoConfig = {
5
+ level,
6
+ base: context,
7
+ timestamp: pino.stdTimeFunctions.isoTime,
8
+ };
9
+ if (pretty) {
10
+ return pino(pinoConfig, pino.transport({
11
+ target: 'pino-pretty',
12
+ options: {
13
+ colorize: true,
14
+ translateTime: 'HH:MM:ss.l',
15
+ ignore: 'pid,hostname',
16
+ singleLine: false,
17
+ },
18
+ }));
19
+ }
20
+ return pino(pinoConfig);
21
+ }
22
+ export let DEFAULT_LOGGER = createLogger({
23
+ level: process.env.LOG_LEVEL || 'info',
24
+ pretty: process.env.NODE_ENV !== 'production',
25
+ });
26
+ export function setDefaultLogger(logger) {
27
+ DEFAULT_LOGGER = logger;
28
+ }
29
+ export function getLogger(context) {
30
+ return DEFAULT_LOGGER.child(context);
31
+ }
@@ -0,0 +1,5 @@
1
+ import { type Observable } from 'rxjs';
2
+ import type { AnyEvent } from '../types/event';
3
+ import type { Message } from '../types/message';
4
+ import type { LoopConfig, TurnContext } from './types';
5
+ export declare const runLoop: (context: TurnContext, config: LoopConfig, history: Message[]) => Observable<AnyEvent>;