@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,313 @@
1
+ /**
2
+ * stateEventProcessor
3
+ *
4
+ * Stateless event transformer: Stream Events → State Events
5
+ *
6
+ * Input Events (Stream Layer):
7
+ * - message_start
8
+ * - message_stop
9
+ * - text_delta (triggers responding state)
10
+ * - tool_use_start
11
+ * - tool_use_stop
12
+ *
13
+ * Output Events (State Layer):
14
+ * - conversation_start
15
+ * - conversation_responding
16
+ * - conversation_end
17
+ * - tool_planned
18
+ * - tool_executing
19
+ */
20
+
21
+ import type { Processor, ProcessorDefinition } from "../mealy";
22
+ import type {
23
+ // Base type
24
+ EngineEvent,
25
+ // Input: StreamEvent (from agent layer)
26
+ StreamEvent,
27
+ MessageStartEvent,
28
+ MessageStopEvent,
29
+ ToolUseStartEvent,
30
+ // Output: State events
31
+ ConversationStartEvent,
32
+ ConversationRespondingEvent,
33
+ ConversationEndEvent,
34
+ ConversationInterruptedEvent,
35
+ ToolPlannedEvent,
36
+ ToolExecutingEvent,
37
+ ErrorOccurredEvent,
38
+ } from "../../types";
39
+ import { createLogger } from "commonxjs/logger";
40
+
41
+ const logger = createLogger("engine/stateEventProcessor");
42
+
43
+ // ===== State Types =====
44
+
45
+ /**
46
+ * StateEventProcessorContext
47
+ *
48
+ * Minimal context needed for event transformation logic.
49
+ * Does NOT track agent state - only auxiliary info for decision-making.
50
+ *
51
+ * Currently empty - no context needed as all information comes from events.
52
+ */
53
+ export interface StateEventProcessorContext {
54
+ // Empty - all information comes from events
55
+ }
56
+
57
+ /**
58
+ * Initial context factory for StateEventProcessor
59
+ */
60
+ export function createInitialStateEventProcessorContext(): StateEventProcessorContext {
61
+ return {};
62
+ }
63
+
64
+ // ===== Processor Implementation =====
65
+
66
+ /**
67
+ * Output event types from StateEventProcessor
68
+ */
69
+ export type StateEventProcessorOutput =
70
+ | ConversationStartEvent
71
+ | ConversationRespondingEvent
72
+ | ConversationEndEvent
73
+ | ConversationInterruptedEvent
74
+ | ToolPlannedEvent
75
+ | ToolExecutingEvent
76
+ | ErrorOccurredEvent;
77
+
78
+ /**
79
+ * Input event types for StateEventProcessor
80
+ */
81
+ export type StateEventProcessorInput = StreamEvent;
82
+
83
+ /**
84
+ * stateEventProcessor
85
+ *
86
+ * Stateless event transformer: Stream Events → State Events
87
+ *
88
+ * Design:
89
+ * - Does NOT track agent state (that's StateMachine's job)
90
+ * - Only maintains auxiliary context (timestamps, etc.)
91
+ * - Emits State Events that StateMachine consumes
92
+ *
93
+ * Pattern: (context, input) => [newContext, outputs]
94
+ */
95
+ export const stateEventProcessor: Processor<
96
+ StateEventProcessorContext,
97
+ StateEventProcessorInput,
98
+ StateEventProcessorOutput
99
+ > = (context, input): [StateEventProcessorContext, StateEventProcessorOutput[]] => {
100
+ // Log all incoming Stream Events
101
+ logger.debug(`[Stream Event] ${input.type}`, {
102
+ context,
103
+ eventData: "data" in input ? input.data : undefined,
104
+ });
105
+
106
+ switch (input.type) {
107
+ case "message_start":
108
+ return handleMessageStart(context, input);
109
+
110
+ case "message_delta":
111
+ return handleMessageDelta(context);
112
+
113
+ case "message_stop":
114
+ return handleMessageStop(context, input);
115
+
116
+ case "text_delta":
117
+ return handleTextDelta(context);
118
+
119
+ case "tool_use_start":
120
+ return handleToolUseStart(context, input);
121
+
122
+ case "tool_use_stop":
123
+ return handleToolUseStop(context);
124
+
125
+ case "error_received":
126
+ return handleErrorReceived(context, input);
127
+
128
+ default:
129
+ // Pass through unhandled events
130
+ logger.debug(`[Stream Event] ${input.type} (unhandled)`);
131
+ return [context, []];
132
+ }
133
+ };
134
+
135
+ /**
136
+ * Handle message_start event
137
+ *
138
+ * Emits: conversation_start
139
+ */
140
+ function handleMessageStart(
141
+ context: Readonly<StateEventProcessorContext>,
142
+ event: StreamEvent
143
+ ): [StateEventProcessorContext, StateEventProcessorOutput[]] {
144
+ const { data } = event as MessageStartEvent;
145
+
146
+ const conversationStartEvent: ConversationStartEvent = {
147
+ type: "conversation_start",
148
+ timestamp: Date.now(),
149
+ data: {
150
+ messageId: data.messageId,
151
+ },
152
+ };
153
+
154
+ return [context, [conversationStartEvent]];
155
+ }
156
+
157
+ /**
158
+ * Handle message_delta event
159
+ *
160
+ * No longer needed as stopReason is now in message_stop event.
161
+ * Kept for compatibility with event routing.
162
+ */
163
+ function handleMessageDelta(
164
+ context: Readonly<StateEventProcessorContext>
165
+ ): [StateEventProcessorContext, StateEventProcessorOutput[]] {
166
+ // No-op: stopReason now comes from message_stop
167
+ return [context, []];
168
+ }
169
+
170
+ /**
171
+ * Handle message_stop event
172
+ *
173
+ * Emits: conversation_end (only if stopReason is NOT "tool_use")
174
+ *
175
+ * This event signals that Claude has finished streaming a message.
176
+ * However, if stopReason is "tool_use", the conversation continues
177
+ * because Claude will execute tools and send more messages.
178
+ */
179
+ function handleMessageStop(
180
+ context: Readonly<StateEventProcessorContext>,
181
+ event: StreamEvent
182
+ ): [StateEventProcessorContext, StateEventProcessorOutput[]] {
183
+ const { data } = event as MessageStopEvent;
184
+ const stopReason = data.stopReason;
185
+
186
+ logger.debug("message_stop received", { stopReason });
187
+
188
+ // If stopReason is "tool_use", don't emit conversation_end
189
+ // The conversation continues after tool execution
190
+ if (stopReason === "tool_use") {
191
+ logger.debug("Skipping conversation_end (tool_use in progress)");
192
+ return [context, []];
193
+ }
194
+
195
+ // For all other cases (end_turn, max_tokens, etc.), emit conversation_end
196
+ const conversationEndEvent: ConversationEndEvent = {
197
+ type: "conversation_end",
198
+ timestamp: Date.now(),
199
+ data: {
200
+ reason: "completed",
201
+ },
202
+ };
203
+
204
+ return [context, [conversationEndEvent]];
205
+ }
206
+
207
+ /**
208
+ * Handle text_delta event
209
+ *
210
+ * Emits: conversation_responding
211
+ */
212
+ function handleTextDelta(
213
+ context: Readonly<StateEventProcessorContext>
214
+ ): [StateEventProcessorContext, StateEventProcessorOutput[]] {
215
+ const respondingEvent: ConversationRespondingEvent = {
216
+ type: "conversation_responding",
217
+ timestamp: Date.now(),
218
+ data: {},
219
+ };
220
+
221
+ return [context, [respondingEvent]];
222
+ }
223
+
224
+ /**
225
+ * Handle tool_use_start event
226
+ *
227
+ * Emits: tool_planned, tool_executing
228
+ */
229
+ function handleToolUseStart(
230
+ context: Readonly<StateEventProcessorContext>,
231
+ event: StreamEvent
232
+ ): [StateEventProcessorContext, StateEventProcessorOutput[]] {
233
+ const { data } = event as ToolUseStartEvent;
234
+ const outputs: StateEventProcessorOutput[] = [];
235
+
236
+ // Emit ToolPlannedEvent
237
+ const toolPlannedEvent: ToolPlannedEvent = {
238
+ type: "tool_planned",
239
+ timestamp: Date.now(),
240
+ data: {
241
+ toolId: data.toolCallId,
242
+ toolName: data.toolName,
243
+ },
244
+ };
245
+ outputs.push(toolPlannedEvent);
246
+
247
+ // Emit ToolExecutingEvent
248
+ const toolExecutingEvent: ToolExecutingEvent = {
249
+ type: "tool_executing",
250
+ timestamp: Date.now(),
251
+ data: {
252
+ toolId: data.toolCallId,
253
+ toolName: data.toolName,
254
+ input: {},
255
+ },
256
+ };
257
+ outputs.push(toolExecutingEvent);
258
+
259
+ return [context, outputs];
260
+ }
261
+
262
+ /**
263
+ * Handle tool_use_stop event
264
+ *
265
+ * Pass through - no State Event emitted.
266
+ * StateMachine handles the state transition internally.
267
+ */
268
+ function handleToolUseStop(
269
+ context: Readonly<StateEventProcessorContext>
270
+ ): [StateEventProcessorContext, StateEventProcessorOutput[]] {
271
+ // Pass through - no State Event
272
+ return [context, []];
273
+ }
274
+
275
+ /**
276
+ * Handle error_received event
277
+ *
278
+ * Emits: error_occurred
279
+ */
280
+ function handleErrorReceived(
281
+ context: Readonly<StateEventProcessorContext>,
282
+ event: StreamEvent
283
+ ): [StateEventProcessorContext, StateEventProcessorOutput[]] {
284
+ const { data } = event as EngineEvent<"error_received", { message: string; errorCode?: string }>;
285
+
286
+ const errorOccurredEvent: ErrorOccurredEvent = {
287
+ type: "error_occurred",
288
+ timestamp: Date.now(),
289
+ data: {
290
+ code: data.errorCode || "unknown_error",
291
+ message: data.message,
292
+ recoverable: true,
293
+ },
294
+ };
295
+
296
+ return [context, [errorOccurredEvent]];
297
+ }
298
+
299
+ /**
300
+ * StateEvent Processor Definition
301
+ *
302
+ * Stateless event transformer: Stream Events → State Events
303
+ */
304
+ export const stateEventProcessorDef: ProcessorDefinition<
305
+ StateEventProcessorContext,
306
+ StateEventProcessorInput,
307
+ StateEventProcessorOutput
308
+ > = {
309
+ name: "StateEventProcessor",
310
+ description: "Transform Stream Events into State Events",
311
+ initialState: createInitialStateEventProcessorContext,
312
+ processor: stateEventProcessor,
313
+ };
@@ -0,0 +1,239 @@
1
+ /**
2
+ * turnTrackerProcessor
3
+ *
4
+ * Pure Mealy transition function that tracks request-response turn pairs.
5
+ *
6
+ * Input Events:
7
+ * - user_message (Message Layer)
8
+ * - message_stop (Stream Layer - contains stop reason)
9
+ * - assistant_message (Message Layer)
10
+ *
11
+ * Output Events (Turn Layer):
12
+ * - turn_request
13
+ * - turn_response
14
+ */
15
+
16
+ import type { Processor, ProcessorDefinition } from "../mealy";
17
+ import type {
18
+ // Input: combined stream and message events
19
+ StreamEvent,
20
+ AgentMessageEvent,
21
+ MessageStopEvent,
22
+ UserMessageEvent,
23
+ // Output: Turn events
24
+ TurnRequestEvent,
25
+ TurnResponseEvent,
26
+ // Data types
27
+ TokenUsage,
28
+ } from "../../types";
29
+
30
+ // ===== State Types =====
31
+
32
+ /**
33
+ * Pending turn tracking
34
+ */
35
+ export interface PendingTurn {
36
+ turnId: string;
37
+ messageId: string;
38
+ content: string;
39
+ requestedAt: number;
40
+ }
41
+
42
+ /**
43
+ * TurnTrackerState
44
+ *
45
+ * Tracks the current turn state.
46
+ */
47
+ export interface TurnTrackerState {
48
+ /**
49
+ * Currently pending turn (waiting for response)
50
+ */
51
+ pendingTurn: PendingTurn | null;
52
+
53
+ /**
54
+ * Cost per input token (USD)
55
+ */
56
+ costPerInputToken: number;
57
+
58
+ /**
59
+ * Cost per output token (USD)
60
+ */
61
+ costPerOutputToken: number;
62
+ }
63
+
64
+ /**
65
+ * Initial state factory for TurnTracker
66
+ */
67
+ export function createInitialTurnTrackerState(): TurnTrackerState {
68
+ return {
69
+ pendingTurn: null,
70
+ costPerInputToken: 0.000003, // $3 per 1M tokens
71
+ costPerOutputToken: 0.000015, // $15 per 1M tokens
72
+ };
73
+ }
74
+
75
+ // ===== Processor Implementation =====
76
+
77
+ /**
78
+ * Generate a unique ID
79
+ */
80
+ function generateId(): string {
81
+ return `turn_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
82
+ }
83
+
84
+ /**
85
+ * Output event types from TurnTracker
86
+ */
87
+ export type TurnTrackerOutput = TurnRequestEvent | TurnResponseEvent;
88
+
89
+ /**
90
+ * Input event types for TurnTracker
91
+ * Accepts both Stream and Message layer events
92
+ */
93
+ export type TurnTrackerInput = StreamEvent | AgentMessageEvent;
94
+
95
+ /**
96
+ * turnTrackerProcessor
97
+ *
98
+ * Pure Mealy transition function for turn tracking.
99
+ * Pattern: (state, input) => [newState, outputs]
100
+ */
101
+ export const turnTrackerProcessor: Processor<
102
+ TurnTrackerState,
103
+ TurnTrackerInput,
104
+ TurnTrackerOutput
105
+ > = (state, input): [TurnTrackerState, TurnTrackerOutput[]] => {
106
+ switch (input.type) {
107
+ case "user_message":
108
+ return handleUserMessage(state, input as AgentMessageEvent);
109
+
110
+ case "message_stop":
111
+ return handleMessageStop(state, input as StreamEvent);
112
+
113
+ case "assistant_message":
114
+ // Turn completion is handled in message_stop
115
+ // This handler is kept for potential future use
116
+ return [state, []];
117
+
118
+ default:
119
+ return [state, []];
120
+ }
121
+ };
122
+
123
+ /**
124
+ * Handle user_message event
125
+ */
126
+ function handleUserMessage(
127
+ state: Readonly<TurnTrackerState>,
128
+ event: AgentMessageEvent
129
+ ): [TurnTrackerState, TurnTrackerOutput[]] {
130
+ const { data } = event as UserMessageEvent;
131
+ const turnId = generateId();
132
+
133
+ // Extract content as string (UserMessage.content can be string or array)
134
+ const contentText = typeof data.content === "string" ? data.content : "";
135
+
136
+ const pendingTurn: PendingTurn = {
137
+ turnId,
138
+ messageId: data.id,
139
+ content: contentText,
140
+ requestedAt: event.timestamp,
141
+ };
142
+
143
+ const turnRequestEvent: TurnRequestEvent = {
144
+ type: "turn_request",
145
+ timestamp: Date.now(),
146
+ data: {
147
+ turnId,
148
+ messageId: data.id,
149
+ content: contentText,
150
+ timestamp: event.timestamp,
151
+ },
152
+ };
153
+
154
+ return [
155
+ {
156
+ ...state,
157
+ pendingTurn,
158
+ },
159
+ [turnRequestEvent],
160
+ ];
161
+ }
162
+
163
+ /**
164
+ * Handle message_stop event
165
+ */
166
+ function handleMessageStop(
167
+ state: Readonly<TurnTrackerState>,
168
+ event: StreamEvent
169
+ ): [TurnTrackerState, TurnTrackerOutput[]] {
170
+ if (!state.pendingTurn) {
171
+ return [state, []];
172
+ }
173
+
174
+ const { data } = event as MessageStopEvent;
175
+ const stopReason = data.stopReason;
176
+
177
+ // Complete turn based on stop reason
178
+ // - "end_turn": Normal completion (no tool use)
179
+ // - "tool_use": Tool calling in progress, DON'T complete yet
180
+ // - "max_tokens": Hit token limit, complete turn
181
+ // - "stop_sequence": Hit stop sequence, complete turn
182
+ if (stopReason === "end_turn" || stopReason === "max_tokens" || stopReason === "stop_sequence") {
183
+ return completeTurn(state, event.timestamp);
184
+ }
185
+
186
+ // For tool_use, don't complete turn yet
187
+ return [state, []];
188
+ }
189
+
190
+ /**
191
+ * Complete the turn and emit TurnResponseEvent
192
+ */
193
+ function completeTurn(
194
+ state: Readonly<TurnTrackerState>,
195
+ completedAt: number
196
+ ): [TurnTrackerState, TurnTrackerOutput[]] {
197
+ if (!state.pendingTurn) {
198
+ return [state, []];
199
+ }
200
+
201
+ const { turnId, messageId, requestedAt } = state.pendingTurn;
202
+ const duration = completedAt - requestedAt;
203
+
204
+ const usage: TokenUsage = { inputTokens: 0, outputTokens: 0 };
205
+
206
+ const turnResponseEvent: TurnResponseEvent = {
207
+ type: "turn_response",
208
+ timestamp: Date.now(),
209
+ data: {
210
+ turnId,
211
+ messageId,
212
+ duration,
213
+ usage,
214
+ timestamp: completedAt,
215
+ },
216
+ };
217
+
218
+ return [
219
+ {
220
+ ...state,
221
+ pendingTurn: null,
222
+ },
223
+ [turnResponseEvent],
224
+ ];
225
+ }
226
+
227
+ /**
228
+ * TurnTracker Processor Definition
229
+ */
230
+ export const turnTrackerProcessorDef: ProcessorDefinition<
231
+ TurnTrackerState,
232
+ TurnTrackerInput,
233
+ TurnTrackerOutput
234
+ > = {
235
+ name: "TurnTracker",
236
+ description: "Tracks request-response turn pairs",
237
+ initialState: createInitialTurnTrackerState,
238
+ processor: turnTrackerProcessor,
239
+ };