@agentxjs/core 1.9.1-dev

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 (77) hide show
  1. package/package.json +31 -0
  2. package/src/agent/AgentStateMachine.ts +151 -0
  3. package/src/agent/README.md +296 -0
  4. package/src/agent/__tests__/AgentStateMachine.test.ts +346 -0
  5. package/src/agent/__tests__/createAgent.test.ts +728 -0
  6. package/src/agent/__tests__/engine/internal/messageAssemblerProcessor.test.ts +567 -0
  7. package/src/agent/__tests__/engine/internal/stateEventProcessor.test.ts +315 -0
  8. package/src/agent/__tests__/engine/internal/turnTrackerProcessor.test.ts +340 -0
  9. package/src/agent/__tests__/engine/mealy/Mealy.test.ts +370 -0
  10. package/src/agent/__tests__/engine/mealy/Store.test.ts +123 -0
  11. package/src/agent/__tests__/engine/mealy/combinators.test.ts +322 -0
  12. package/src/agent/createAgent.ts +467 -0
  13. package/src/agent/engine/AgentProcessor.ts +106 -0
  14. package/src/agent/engine/MealyMachine.ts +184 -0
  15. package/src/agent/engine/internal/index.ts +35 -0
  16. package/src/agent/engine/internal/messageAssemblerProcessor.ts +550 -0
  17. package/src/agent/engine/internal/stateEventProcessor.ts +313 -0
  18. package/src/agent/engine/internal/turnTrackerProcessor.ts +239 -0
  19. package/src/agent/engine/mealy/Mealy.ts +308 -0
  20. package/src/agent/engine/mealy/Processor.ts +70 -0
  21. package/src/agent/engine/mealy/Sink.ts +56 -0
  22. package/src/agent/engine/mealy/Source.ts +51 -0
  23. package/src/agent/engine/mealy/Store.ts +98 -0
  24. package/src/agent/engine/mealy/combinators.ts +176 -0
  25. package/src/agent/engine/mealy/index.ts +45 -0
  26. package/src/agent/index.ts +106 -0
  27. package/src/agent/types/engine.ts +395 -0
  28. package/src/agent/types/event.ts +478 -0
  29. package/src/agent/types/index.ts +197 -0
  30. package/src/agent/types/message.ts +387 -0
  31. package/src/common/index.ts +8 -0
  32. package/src/common/logger/ConsoleLogger.ts +137 -0
  33. package/src/common/logger/LoggerFactoryImpl.ts +123 -0
  34. package/src/common/logger/index.ts +26 -0
  35. package/src/common/logger/types.ts +98 -0
  36. package/src/container/Container.ts +185 -0
  37. package/src/container/index.ts +44 -0
  38. package/src/container/types.ts +71 -0
  39. package/src/driver/index.ts +42 -0
  40. package/src/driver/types.ts +363 -0
  41. package/src/event/EventBus.ts +260 -0
  42. package/src/event/README.md +237 -0
  43. package/src/event/__tests__/EventBus.test.ts +251 -0
  44. package/src/event/index.ts +46 -0
  45. package/src/event/types/agent.ts +512 -0
  46. package/src/event/types/base.ts +241 -0
  47. package/src/event/types/bus.ts +429 -0
  48. package/src/event/types/command.ts +749 -0
  49. package/src/event/types/container.ts +471 -0
  50. package/src/event/types/driver.ts +452 -0
  51. package/src/event/types/index.ts +26 -0
  52. package/src/event/types/session.ts +314 -0
  53. package/src/image/Image.ts +203 -0
  54. package/src/image/index.ts +36 -0
  55. package/src/image/types.ts +77 -0
  56. package/src/index.ts +20 -0
  57. package/src/mq/OffsetGenerator.ts +48 -0
  58. package/src/mq/README.md +166 -0
  59. package/src/mq/__tests__/OffsetGenerator.test.ts +121 -0
  60. package/src/mq/index.ts +18 -0
  61. package/src/mq/types.ts +172 -0
  62. package/src/network/RpcClient.ts +455 -0
  63. package/src/network/index.ts +76 -0
  64. package/src/network/jsonrpc.ts +336 -0
  65. package/src/network/protocol.ts +90 -0
  66. package/src/network/types.ts +284 -0
  67. package/src/persistence/index.ts +27 -0
  68. package/src/persistence/types.ts +226 -0
  69. package/src/runtime/AgentXRuntime.ts +501 -0
  70. package/src/runtime/index.ts +56 -0
  71. package/src/runtime/types.ts +236 -0
  72. package/src/session/Session.ts +71 -0
  73. package/src/session/index.ts +25 -0
  74. package/src/session/types.ts +77 -0
  75. package/src/workspace/index.ts +27 -0
  76. package/src/workspace/types.ts +131 -0
  77. package/tsconfig.json +10 -0
@@ -0,0 +1,550 @@
1
+ /**
2
+ * messageAssemblerProcessor
3
+ *
4
+ * Pure Mealy transition function that assembles complete Message Layer events
5
+ * from Stream Layer events.
6
+ *
7
+ * Input Events (Stream Layer):
8
+ * - message_start
9
+ * - text_delta
10
+ * - tool_use_start
11
+ * - input_json_delta
12
+ * - tool_use_stop
13
+ * - tool_result
14
+ * - message_stop
15
+ *
16
+ * Output Events (Message Layer):
17
+ * - tool_call_message (Message - AI's request to call a tool)
18
+ * - tool_result_message (Message - tool execution result)
19
+ * - assistant_message (Message - complete assistant response)
20
+ */
21
+
22
+ import type { Processor, ProcessorDefinition } from "../mealy";
23
+ import type {
24
+ // Input: StreamEvent (from agent layer)
25
+ StreamEvent,
26
+ MessageStartEvent,
27
+ TextDeltaEvent,
28
+ ToolUseStartEvent,
29
+ InputJsonDeltaEvent,
30
+ ToolResultEvent,
31
+ MessageStopEvent,
32
+ // Output: Message events
33
+ AssistantMessageEvent,
34
+ ToolCallMessageEvent,
35
+ ToolResultMessageEvent,
36
+ ErrorMessageEvent,
37
+ // Message types
38
+ AssistantMessage,
39
+ ToolCallMessage,
40
+ ToolResultMessage,
41
+ ErrorMessage,
42
+ // Content parts
43
+ TextPart,
44
+ ToolCallPart,
45
+ ToolResultPart,
46
+ } from "../../types";
47
+
48
+ // ===== State Types =====
49
+
50
+ /**
51
+ * Pending content accumulator
52
+ */
53
+ export interface PendingContent {
54
+ type: "text" | "tool_use";
55
+ index: number;
56
+ // For text content
57
+ textDeltas?: string[];
58
+ // For tool use
59
+ toolId?: string;
60
+ toolName?: string;
61
+ toolInputJson?: string;
62
+ }
63
+
64
+ /**
65
+ * Pending tool call info (for matching with tool_result)
66
+ */
67
+ export interface PendingToolCall {
68
+ id: string;
69
+ name: string;
70
+ }
71
+
72
+ /**
73
+ * MessageAssemblerState
74
+ *
75
+ * Tracks the state of message assembly from stream events.
76
+ */
77
+ export interface MessageAssemblerState {
78
+ /**
79
+ * Current message ID being assembled
80
+ */
81
+ currentMessageId: string | null;
82
+
83
+ /**
84
+ * Timestamp when the current message started
85
+ */
86
+ messageStartTime: number | null;
87
+
88
+ /**
89
+ * Pending content blocks being accumulated
90
+ * Key is the content block index
91
+ */
92
+ pendingContents: Record<number, PendingContent>;
93
+
94
+ /**
95
+ * Pending tool calls waiting for results
96
+ * Key is the tool call ID
97
+ */
98
+ pendingToolCalls: Record<string, PendingToolCall>;
99
+ }
100
+
101
+ /**
102
+ * Initial state factory for MessageAssembler
103
+ */
104
+ export function createInitialMessageAssemblerState(): MessageAssemblerState {
105
+ return {
106
+ currentMessageId: null,
107
+ messageStartTime: null,
108
+ pendingContents: {},
109
+ pendingToolCalls: {},
110
+ };
111
+ }
112
+
113
+ // ===== Processor Implementation =====
114
+
115
+ /**
116
+ * Generate a unique ID
117
+ */
118
+ function generateId(): string {
119
+ return `msg_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
120
+ }
121
+
122
+ /**
123
+ * Output event types from MessageAssembler
124
+ */
125
+ export type MessageAssemblerOutput =
126
+ | AssistantMessageEvent
127
+ | ToolCallMessageEvent
128
+ | ToolResultMessageEvent
129
+ | ErrorMessageEvent;
130
+
131
+ /**
132
+ * Input event types for MessageAssembler
133
+ */
134
+ export type MessageAssemblerInput = StreamEvent;
135
+
136
+ /**
137
+ * messageAssemblerProcessor
138
+ *
139
+ * Pure Mealy transition function for message assembly.
140
+ * Pattern: (state, input) => [newState, outputs]
141
+ */
142
+ export const messageAssemblerProcessor: Processor<
143
+ MessageAssemblerState,
144
+ MessageAssemblerInput,
145
+ MessageAssemblerOutput
146
+ > = (state, input): [MessageAssemblerState, MessageAssemblerOutput[]] => {
147
+ switch (input.type) {
148
+ case "message_start":
149
+ return handleMessageStart(state, input);
150
+
151
+ case "text_delta":
152
+ return handleTextDelta(state, input);
153
+
154
+ case "tool_use_start":
155
+ return handleToolUseStart(state, input);
156
+
157
+ case "input_json_delta":
158
+ return handleInputJsonDelta(state, input);
159
+
160
+ case "tool_use_stop":
161
+ return handleToolUseStop(state, input);
162
+
163
+ case "tool_result":
164
+ return handleToolResult(state, input);
165
+
166
+ case "message_stop":
167
+ return handleMessageStop(state, input);
168
+
169
+ case "error_received":
170
+ return handleErrorReceived(state, input);
171
+
172
+ default:
173
+ // Pass through unhandled events (no state change, no output)
174
+ return [state, []];
175
+ }
176
+ };
177
+
178
+ /**
179
+ * Handle message_start event
180
+ */
181
+ function handleMessageStart(
182
+ state: Readonly<MessageAssemblerState>,
183
+ event: StreamEvent
184
+ ): [MessageAssemblerState, MessageAssemblerOutput[]] {
185
+ const { data } = event as MessageStartEvent;
186
+ return [
187
+ {
188
+ ...state,
189
+ currentMessageId: data.messageId,
190
+ messageStartTime: event.timestamp,
191
+ pendingContents: {},
192
+ },
193
+ [],
194
+ ];
195
+ }
196
+
197
+ /**
198
+ * Handle text_delta event
199
+ */
200
+ function handleTextDelta(
201
+ state: Readonly<MessageAssemblerState>,
202
+ event: StreamEvent
203
+ ): [MessageAssemblerState, MessageAssemblerOutput[]] {
204
+ const { data } = event as TextDeltaEvent;
205
+ const index = 0; // Text content uses index 0
206
+ const existingContent = state.pendingContents[index];
207
+
208
+ const pendingContent: PendingContent =
209
+ existingContent?.type === "text"
210
+ ? {
211
+ ...existingContent,
212
+ textDeltas: [...(existingContent.textDeltas || []), data.text],
213
+ }
214
+ : {
215
+ type: "text",
216
+ index,
217
+ textDeltas: [data.text],
218
+ };
219
+
220
+ return [
221
+ {
222
+ ...state,
223
+ pendingContents: {
224
+ ...state.pendingContents,
225
+ [index]: pendingContent,
226
+ },
227
+ },
228
+ [],
229
+ ];
230
+ }
231
+
232
+ /**
233
+ * Handle tool_use_start event
234
+ */
235
+ function handleToolUseStart(
236
+ state: Readonly<MessageAssemblerState>,
237
+ event: StreamEvent
238
+ ): [MessageAssemblerState, MessageAssemblerOutput[]] {
239
+ const { data } = event as ToolUseStartEvent;
240
+ const index = 1; // Tool use uses index 1
241
+
242
+ const pendingContent: PendingContent = {
243
+ type: "tool_use",
244
+ index,
245
+ toolId: data.toolCallId,
246
+ toolName: data.toolName,
247
+ toolInputJson: "",
248
+ };
249
+
250
+ return [
251
+ {
252
+ ...state,
253
+ pendingContents: {
254
+ ...state.pendingContents,
255
+ [index]: pendingContent,
256
+ },
257
+ },
258
+ [],
259
+ ];
260
+ }
261
+
262
+ /**
263
+ * Handle input_json_delta event
264
+ */
265
+ function handleInputJsonDelta(
266
+ state: Readonly<MessageAssemblerState>,
267
+ event: StreamEvent
268
+ ): [MessageAssemblerState, MessageAssemblerOutput[]] {
269
+ const { data } = event as InputJsonDeltaEvent;
270
+ const index = 1; // Tool use uses index 1
271
+ const existingContent = state.pendingContents[index];
272
+
273
+ if (!existingContent || existingContent.type !== "tool_use") {
274
+ // No pending tool_use content, ignore
275
+ return [state, []];
276
+ }
277
+
278
+ const pendingContent: PendingContent = {
279
+ ...existingContent,
280
+ toolInputJson: (existingContent.toolInputJson || "") + data.partialJson,
281
+ };
282
+
283
+ return [
284
+ {
285
+ ...state,
286
+ pendingContents: {
287
+ ...state.pendingContents,
288
+ [index]: pendingContent,
289
+ },
290
+ },
291
+ [],
292
+ ];
293
+ }
294
+
295
+ /**
296
+ * Handle tool_use_stop event
297
+ *
298
+ * Emits:
299
+ * - tool_call_message (Message Event) - for UI display and tool execution
300
+ */
301
+ function handleToolUseStop(
302
+ state: Readonly<MessageAssemblerState>,
303
+ _event: StreamEvent
304
+ ): [MessageAssemblerState, MessageAssemblerOutput[]] {
305
+ const index = 1;
306
+ const pendingContent = state.pendingContents[index];
307
+
308
+ if (!pendingContent || pendingContent.type !== "tool_use") {
309
+ return [state, []];
310
+ }
311
+
312
+ // Get tool info from pendingContent (saved during tool_use_start)
313
+ const toolId = pendingContent.toolId || "";
314
+ const toolName = pendingContent.toolName || "";
315
+
316
+ // Parse tool input JSON (accumulated during input_json_delta)
317
+ let toolInput: Record<string, unknown> = {};
318
+ try {
319
+ toolInput = pendingContent.toolInputJson ? JSON.parse(pendingContent.toolInputJson) : {};
320
+ } catch {
321
+ // Failed to parse, use empty object
322
+ toolInput = {};
323
+ }
324
+
325
+ // Create ToolCallPart
326
+ const toolCall: ToolCallPart = {
327
+ type: "tool-call",
328
+ id: toolId,
329
+ name: toolName,
330
+ input: toolInput,
331
+ };
332
+
333
+ // Create ToolCallMessage (complete Message object)
334
+ // parentId links this tool call to its parent assistant message
335
+ const messageId = generateId();
336
+ const timestamp = Date.now();
337
+ const toolCallMessage: ToolCallMessage = {
338
+ id: messageId,
339
+ role: "assistant",
340
+ subtype: "tool-call",
341
+ toolCall,
342
+ timestamp,
343
+ parentId: state.currentMessageId || undefined,
344
+ };
345
+
346
+ // Emit tool_call_message event - data is complete Message object
347
+ const toolCallMessageEvent: ToolCallMessageEvent = {
348
+ type: "tool_call_message",
349
+ timestamp,
350
+ data: toolCallMessage,
351
+ };
352
+
353
+ // Remove from pending contents, add to pending tool calls
354
+ const { [index]: _, ...remainingContents } = state.pendingContents;
355
+
356
+ return [
357
+ {
358
+ ...state,
359
+ pendingContents: remainingContents,
360
+ pendingToolCalls: {
361
+ ...state.pendingToolCalls,
362
+ [toolId]: { id: toolId, name: toolName },
363
+ },
364
+ },
365
+ [toolCallMessageEvent],
366
+ ];
367
+ }
368
+
369
+ /**
370
+ * Handle tool_result event
371
+ *
372
+ * Emits:
373
+ * - tool_result_message (Message Event) - for UI display
374
+ */
375
+ function handleToolResult(
376
+ state: Readonly<MessageAssemblerState>,
377
+ event: StreamEvent
378
+ ): [MessageAssemblerState, MessageAssemblerOutput[]] {
379
+ const { data } = event as ToolResultEvent;
380
+ const { toolCallId, result, isError } = data;
381
+
382
+ // Find pending tool call
383
+ const pendingToolCall = state.pendingToolCalls[toolCallId];
384
+ const toolName = pendingToolCall?.name || "unknown";
385
+
386
+ // Create tool result part
387
+ const toolResult: ToolResultPart = {
388
+ type: "tool-result",
389
+ id: toolCallId,
390
+ name: toolName,
391
+ output: {
392
+ type: isError ? "error-text" : "text",
393
+ value: typeof result === "string" ? result : JSON.stringify(result),
394
+ },
395
+ };
396
+
397
+ // Create ToolResultMessage (complete Message object)
398
+ const messageId = generateId();
399
+ const timestamp = Date.now();
400
+ const toolResultMessage: ToolResultMessage = {
401
+ id: messageId,
402
+ role: "tool",
403
+ subtype: "tool-result",
404
+ toolCallId,
405
+ toolResult,
406
+ timestamp,
407
+ };
408
+
409
+ // Emit tool_result_message event - data is complete Message object
410
+ const toolResultMessageEvent: ToolResultMessageEvent = {
411
+ type: "tool_result_message",
412
+ timestamp,
413
+ data: toolResultMessage,
414
+ };
415
+
416
+ // Remove from pending tool calls
417
+ const { [toolCallId]: _, ...remainingToolCalls } = state.pendingToolCalls;
418
+
419
+ return [
420
+ {
421
+ ...state,
422
+ pendingToolCalls: remainingToolCalls,
423
+ },
424
+ [toolResultMessageEvent],
425
+ ];
426
+ }
427
+
428
+ /**
429
+ * Handle message_stop event
430
+ */
431
+ function handleMessageStop(
432
+ state: Readonly<MessageAssemblerState>,
433
+ event: StreamEvent
434
+ ): [MessageAssemblerState, MessageAssemblerOutput[]] {
435
+ const { data } = event as MessageStopEvent;
436
+
437
+ if (!state.currentMessageId) {
438
+ return [state, []];
439
+ }
440
+
441
+ // Assemble all text content
442
+ const textParts: string[] = [];
443
+ const sortedContents = Object.values(state.pendingContents).sort((a, b) => a.index - b.index);
444
+
445
+ for (const pending of sortedContents) {
446
+ if (pending.type === "text" && pending.textDeltas) {
447
+ textParts.push(pending.textDeltas.join(""));
448
+ }
449
+ }
450
+
451
+ const textContent = textParts.join("");
452
+
453
+ // Skip empty messages (but preserve pendingToolCalls if stopReason is "tool_use")
454
+ const stopReason = data.stopReason;
455
+ if (!textContent || textContent.trim().length === 0) {
456
+ const shouldPreserveToolCalls = stopReason === "tool_use";
457
+ return [
458
+ {
459
+ ...createInitialMessageAssemblerState(),
460
+ pendingToolCalls: shouldPreserveToolCalls ? state.pendingToolCalls : {},
461
+ },
462
+ [],
463
+ ];
464
+ }
465
+
466
+ // Create content parts (new structure uses ContentPart[])
467
+ const contentParts: TextPart[] = [
468
+ {
469
+ type: "text",
470
+ text: textContent,
471
+ },
472
+ ];
473
+
474
+ // Create AssistantMessage (complete Message object)
475
+ const timestamp = state.messageStartTime || Date.now();
476
+ const assistantMessage: AssistantMessage = {
477
+ id: state.currentMessageId,
478
+ role: "assistant",
479
+ subtype: "assistant",
480
+ content: contentParts,
481
+ timestamp,
482
+ };
483
+
484
+ // Emit AssistantMessageEvent - data is complete Message object
485
+ const assistantEvent: AssistantMessageEvent = {
486
+ type: "assistant_message",
487
+ timestamp,
488
+ data: assistantMessage,
489
+ };
490
+
491
+ // Reset state, but preserve pendingToolCalls if stopReason is "tool_use"
492
+ // (tool_result events arrive after message_stop in tool call scenarios)
493
+ const shouldPreserveToolCalls = stopReason === "tool_use";
494
+
495
+ return [
496
+ {
497
+ ...createInitialMessageAssemblerState(),
498
+ pendingToolCalls: shouldPreserveToolCalls ? state.pendingToolCalls : {},
499
+ },
500
+ [assistantEvent],
501
+ ];
502
+ }
503
+
504
+ /**
505
+ * Handle error_received event
506
+ *
507
+ * Emits: error_message (Message Event) - for UI display
508
+ */
509
+ function handleErrorReceived(
510
+ _state: Readonly<MessageAssemblerState>,
511
+ event: StreamEvent
512
+ ): [MessageAssemblerState, MessageAssemblerOutput[]] {
513
+ const data = event.data as { message: string; errorCode?: string };
514
+
515
+ // Create ErrorMessage (complete Message object)
516
+ const messageId = generateId();
517
+ const timestamp = Date.now();
518
+ const errorMessage: ErrorMessage = {
519
+ id: messageId,
520
+ role: "error",
521
+ subtype: "error",
522
+ content: data.message,
523
+ errorCode: data.errorCode,
524
+ timestamp,
525
+ };
526
+
527
+ // Emit error_message event - data is complete Message object
528
+ const errorMessageEvent: ErrorMessageEvent = {
529
+ type: "error_message",
530
+ timestamp,
531
+ data: errorMessage,
532
+ };
533
+
534
+ // Reset state on error
535
+ return [createInitialMessageAssemblerState(), [errorMessageEvent]];
536
+ }
537
+
538
+ /**
539
+ * MessageAssembler Processor Definition
540
+ */
541
+ export const messageAssemblerProcessorDef: ProcessorDefinition<
542
+ MessageAssemblerState,
543
+ MessageAssemblerInput,
544
+ MessageAssemblerOutput
545
+ > = {
546
+ name: "MessageAssembler",
547
+ description: "Assembles complete messages from stream events",
548
+ initialState: createInitialMessageAssemblerState,
549
+ processor: messageAssemblerProcessor,
550
+ };