@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,363 @@
1
+ /**
2
+ * Driver Types - LLM Communication Layer
3
+ *
4
+ * Driver is the bridge between AgentX and external LLM (Claude, OpenAI, etc.)
5
+ *
6
+ * ```
7
+ * AgentX
8
+ * │
9
+ * receive() │ AsyncIterable<StreamEvent>
10
+ * ─────────► │ ◄─────────────────────────
11
+ * │
12
+ * ┌───────────────┐
13
+ * │ Driver │
14
+ * │ │
15
+ * │ UserMessage │
16
+ * │ ↓ │
17
+ * │ [SDK call] │
18
+ * │ ↓ │
19
+ * │ StreamEvent │
20
+ * └───────────────┘
21
+ * │
22
+ * ▼
23
+ * External LLM
24
+ * (Claude SDK)
25
+ * ```
26
+ *
27
+ * Key Design:
28
+ * - Driver = single session communication (like Kimi SDK's Session)
29
+ * - Clear input/output boundary (for recording/playback)
30
+ * - Configuration defined by us (capability boundary)
31
+ */
32
+
33
+ import type { UserMessage } from "../agent/types/message";
34
+
35
+ // ============================================================================
36
+ // MCP Server Configuration
37
+ // ============================================================================
38
+
39
+ /**
40
+ * MCP Server configuration
41
+ *
42
+ * Defines how to launch an MCP server process.
43
+ */
44
+ export interface McpServerConfig {
45
+ /**
46
+ * Command to run the MCP server
47
+ */
48
+ command: string;
49
+
50
+ /**
51
+ * Command arguments
52
+ */
53
+ args?: string[];
54
+
55
+ /**
56
+ * Environment variables for the process
57
+ */
58
+ env?: Record<string, string>;
59
+ }
60
+
61
+ // ============================================================================
62
+ // Stream Event (Lightweight)
63
+ // ============================================================================
64
+
65
+ /**
66
+ * StopReason - Why the LLM stopped generating
67
+ */
68
+ export type StopReason =
69
+ | "end_turn"
70
+ | "max_tokens"
71
+ | "tool_use"
72
+ | "stop_sequence"
73
+ | "content_filter"
74
+ | "error"
75
+ | "other";
76
+
77
+ /**
78
+ * StreamEvent - Lightweight event from Driver
79
+ *
80
+ * Only contains essential fields: type, timestamp, data
81
+ * No source, category, intent, context (those are added by upper layers)
82
+ */
83
+ export interface StreamEvent<T extends string = string, D = unknown> {
84
+ readonly type: T;
85
+ readonly timestamp: number;
86
+ readonly data: D;
87
+ }
88
+
89
+ // Stream Event Types
90
+ export interface MessageStartEvent extends StreamEvent<
91
+ "message_start",
92
+ {
93
+ messageId: string;
94
+ model: string;
95
+ }
96
+ > {}
97
+
98
+ export interface MessageStopEvent extends StreamEvent<
99
+ "message_stop",
100
+ {
101
+ stopReason: StopReason;
102
+ }
103
+ > {}
104
+
105
+ export interface TextDeltaEvent extends StreamEvent<
106
+ "text_delta",
107
+ {
108
+ text: string;
109
+ }
110
+ > {}
111
+
112
+ export interface ToolUseStartEvent extends StreamEvent<
113
+ "tool_use_start",
114
+ {
115
+ toolCallId: string;
116
+ toolName: string;
117
+ }
118
+ > {}
119
+
120
+ export interface InputJsonDeltaEvent extends StreamEvent<
121
+ "input_json_delta",
122
+ {
123
+ partialJson: string;
124
+ }
125
+ > {}
126
+
127
+ export interface ToolUseStopEvent extends StreamEvent<
128
+ "tool_use_stop",
129
+ {
130
+ toolCallId: string;
131
+ toolName: string;
132
+ input: Record<string, unknown>;
133
+ }
134
+ > {}
135
+
136
+ export interface ToolResultEvent extends StreamEvent<
137
+ "tool_result",
138
+ {
139
+ toolCallId: string;
140
+ result: unknown;
141
+ isError?: boolean;
142
+ }
143
+ > {}
144
+
145
+ export interface ErrorEvent extends StreamEvent<
146
+ "error",
147
+ {
148
+ message: string;
149
+ errorCode?: string;
150
+ }
151
+ > {}
152
+
153
+ export interface InterruptedEvent extends StreamEvent<
154
+ "interrupted",
155
+ {
156
+ reason: "user" | "timeout" | "error";
157
+ }
158
+ > {}
159
+
160
+ /**
161
+ * DriverStreamEvent - Union of all stream events from Driver
162
+ */
163
+ export type DriverStreamEvent =
164
+ | MessageStartEvent
165
+ | MessageStopEvent
166
+ | TextDeltaEvent
167
+ | ToolUseStartEvent
168
+ | InputJsonDeltaEvent
169
+ | ToolUseStopEvent
170
+ | ToolResultEvent
171
+ | ErrorEvent
172
+ | InterruptedEvent;
173
+
174
+ /**
175
+ * DriverStreamEventType - String literal union of event types
176
+ */
177
+ export type DriverStreamEventType = DriverStreamEvent["type"];
178
+
179
+ // ============================================================================
180
+ // Driver Configuration
181
+ // ============================================================================
182
+
183
+ /**
184
+ * DriverConfig - All configuration for creating a Driver
185
+ *
186
+ * This is our capability boundary - we define what we support.
187
+ * Specific implementations (Claude, OpenAI) must work within this.
188
+ */
189
+ export interface DriverConfig {
190
+ // === Provider Configuration ===
191
+
192
+ /**
193
+ * API key for authentication
194
+ */
195
+ apiKey: string;
196
+
197
+ /**
198
+ * Base URL for API endpoint (optional, for custom deployments)
199
+ */
200
+ baseUrl?: string;
201
+
202
+ /**
203
+ * Model identifier (e.g., "claude-sonnet-4-20250514")
204
+ */
205
+ model?: string;
206
+
207
+ /**
208
+ * Request timeout in milliseconds (default: 600000 = 10 minutes)
209
+ */
210
+ timeout?: number;
211
+
212
+ // === Agent Configuration ===
213
+
214
+ /**
215
+ * Agent ID (for identification and logging)
216
+ */
217
+ agentId: string;
218
+
219
+ /**
220
+ * System prompt for the agent
221
+ */
222
+ systemPrompt?: string;
223
+
224
+ /**
225
+ * Current working directory for tool execution
226
+ */
227
+ cwd?: string;
228
+
229
+ /**
230
+ * MCP servers configuration
231
+ */
232
+ mcpServers?: Record<string, McpServerConfig>;
233
+
234
+ // === Session Configuration ===
235
+
236
+ /**
237
+ * Session ID to resume (for conversation continuity)
238
+ *
239
+ * If provided, Driver will attempt to resume this session.
240
+ * If not provided, a new session is created.
241
+ */
242
+ resumeSessionId?: string;
243
+
244
+ /**
245
+ * Callback when SDK session ID is captured
246
+ *
247
+ * Called once when the session ID becomes available.
248
+ * Save this ID to enable session resume later.
249
+ */
250
+ onSessionIdCaptured?: (sessionId: string) => void;
251
+ }
252
+
253
+ // ============================================================================
254
+ // Driver State
255
+ // ============================================================================
256
+
257
+ /**
258
+ * DriverState - Current state of the Driver
259
+ *
260
+ * - idle: Ready to receive messages
261
+ * - active: Currently processing a message
262
+ * - disposed: Driver has been disposed, cannot be used
263
+ */
264
+ export type DriverState = "idle" | "active" | "disposed";
265
+
266
+ // ============================================================================
267
+ // Driver Interface
268
+ // ============================================================================
269
+
270
+ /**
271
+ * Driver - LLM Communication Interface
272
+ *
273
+ * Responsible for a single session's communication with LLM.
274
+ * Similar to Kimi SDK's Session concept.
275
+ *
276
+ * Lifecycle:
277
+ * 1. createDriver(config) → Driver instance
278
+ * 2. driver.initialize() → Start SDK, MCP servers
279
+ * 3. driver.receive(message) → Send message, get events
280
+ * 4. driver.dispose() → Cleanup
281
+ *
282
+ * @example
283
+ * ```typescript
284
+ * const driver = createDriver(config);
285
+ * await driver.initialize();
286
+ *
287
+ * const events = driver.receive(message);
288
+ * for await (const event of events) {
289
+ * if (event.type === "text_delta") {
290
+ * console.log(event.data.text);
291
+ * }
292
+ * }
293
+ *
294
+ * await driver.dispose();
295
+ * ```
296
+ */
297
+ export interface Driver {
298
+ /**
299
+ * Driver name (for identification and logging)
300
+ */
301
+ readonly name: string;
302
+
303
+ /**
304
+ * SDK Session ID (available after first message)
305
+ */
306
+ readonly sessionId: string | null;
307
+
308
+ /**
309
+ * Current state
310
+ */
311
+ readonly state: DriverState;
312
+
313
+ /**
314
+ * Receive a user message and return stream of events
315
+ *
316
+ * @param message - User message to send
317
+ * @returns AsyncIterable of stream events
318
+ */
319
+ receive(message: UserMessage): AsyncIterable<DriverStreamEvent>;
320
+
321
+ /**
322
+ * Interrupt current operation
323
+ *
324
+ * Stops the current receive() operation gracefully.
325
+ * The AsyncIterable will emit an "interrupted" event and complete.
326
+ */
327
+ interrupt(): void;
328
+
329
+ /**
330
+ * Initialize the Driver
331
+ *
332
+ * Starts SDK subprocess, MCP servers, etc.
333
+ * Must be called before receive().
334
+ */
335
+ initialize(): Promise<void>;
336
+
337
+ /**
338
+ * Dispose and cleanup resources
339
+ *
340
+ * Stops SDK subprocess, MCP servers, etc.
341
+ * Driver cannot be used after dispose().
342
+ */
343
+ dispose(): Promise<void>;
344
+ }
345
+
346
+ // ============================================================================
347
+ // CreateDriver Function Type
348
+ // ============================================================================
349
+
350
+ /**
351
+ * CreateDriver - Factory function type for creating Driver instances
352
+ *
353
+ * Each implementation package exports a function of this type.
354
+ *
355
+ * @example
356
+ * ```typescript
357
+ * // @agentxjs/claude-driver
358
+ * export const createDriver: CreateDriver = (config) => {
359
+ * return new ClaudeDriverImpl(config);
360
+ * };
361
+ * ```
362
+ */
363
+ export type CreateDriver = (config: DriverConfig) => Driver;
@@ -0,0 +1,260 @@
1
+ /**
2
+ * EventBus - Central event bus implementation
3
+ *
4
+ * Pub/Sub event bus for runtime communication.
5
+ * Uses RxJS Subject for reactive event distribution.
6
+ */
7
+
8
+ import type {
9
+ EventBus,
10
+ EventProducer,
11
+ EventConsumer,
12
+ Unsubscribe,
13
+ BusEventHandler,
14
+ SubscribeOptions,
15
+ BusEvent,
16
+ CommandEventMap,
17
+ CommandRequestType,
18
+ ResponseEventFor,
19
+ RequestDataFor,
20
+ CommandRequestResponseMap,
21
+ } from "./types";
22
+ import { Subject } from "rxjs";
23
+ import { createLogger } from "commonxjs/logger";
24
+
25
+ const logger = createLogger("event/EventBus");
26
+
27
+ /**
28
+ * Generate a unique request ID
29
+ *
30
+ * Uses crypto.randomUUID when available (modern browsers and Node.js 19+),
31
+ * falls back to a timestamp-based ID for older environments.
32
+ */
33
+ function generateRequestId(): string {
34
+ // Use crypto.randomUUID if available (browsers and modern Node.js)
35
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
36
+ return `req_${crypto.randomUUID()}`;
37
+ }
38
+ // Fallback for older environments
39
+ return `req_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
40
+ }
41
+
42
+ /**
43
+ * Internal subscription record
44
+ */
45
+ interface Subscription {
46
+ id: number;
47
+ type: string | string[] | "*";
48
+ handler: BusEventHandler;
49
+ filter?: (event: BusEvent) => boolean;
50
+ priority: number;
51
+ once: boolean;
52
+ }
53
+
54
+ /**
55
+ * EventBusImpl - EventBus implementation using RxJS Subject
56
+ */
57
+ export class EventBusImpl implements EventBus {
58
+ private readonly subject = new Subject<BusEvent>();
59
+ private subscriptions: Subscription[] = [];
60
+ private nextId = 0;
61
+ private isDestroyed = false;
62
+
63
+ // Cached restricted views
64
+ private producerView: EventProducer | null = null;
65
+ private consumerView: EventConsumer | null = null;
66
+
67
+ constructor() {
68
+ this.subject.subscribe((event) => {
69
+ this.dispatch(event);
70
+ });
71
+ }
72
+
73
+ emit(event: BusEvent): void {
74
+ if (this.isDestroyed) return;
75
+ this.subject.next(event);
76
+ }
77
+
78
+ emitBatch(events: BusEvent[]): void {
79
+ for (const event of events) {
80
+ this.emit(event);
81
+ }
82
+ }
83
+
84
+ on<T extends string>(
85
+ typeOrTypes: T | string[],
86
+ handler: BusEventHandler<BusEvent & { type: T }>,
87
+ options?: SubscribeOptions<BusEvent & { type: T }>
88
+ ): Unsubscribe {
89
+ if (this.isDestroyed) return () => {};
90
+
91
+ const subscription: Subscription = {
92
+ id: this.nextId++,
93
+ type: typeOrTypes,
94
+ handler: handler as BusEventHandler,
95
+ filter: options?.filter as ((event: BusEvent) => boolean) | undefined,
96
+ priority: options?.priority ?? 0,
97
+ once: options?.once ?? false,
98
+ };
99
+
100
+ this.subscriptions.push(subscription);
101
+ this.sortByPriority();
102
+
103
+ return () => this.removeSubscription(subscription.id);
104
+ }
105
+
106
+ onAny(handler: BusEventHandler, options?: SubscribeOptions): Unsubscribe {
107
+ if (this.isDestroyed) return () => {};
108
+
109
+ const subscription: Subscription = {
110
+ id: this.nextId++,
111
+ type: "*",
112
+ handler,
113
+ filter: options?.filter as ((event: BusEvent) => boolean) | undefined,
114
+ priority: options?.priority ?? 0,
115
+ once: options?.once ?? false,
116
+ };
117
+
118
+ this.subscriptions.push(subscription);
119
+ this.sortByPriority();
120
+
121
+ return () => this.removeSubscription(subscription.id);
122
+ }
123
+
124
+ once<T extends string>(type: T, handler: BusEventHandler<BusEvent & { type: T }>): Unsubscribe {
125
+ return this.on(type, handler, { once: true });
126
+ }
127
+
128
+ onCommand<T extends keyof CommandEventMap>(
129
+ type: T,
130
+ handler: (event: CommandEventMap[T]) => void
131
+ ): Unsubscribe {
132
+ // Reuse the existing on() implementation with type casting
133
+ return this.on(type as string, handler as BusEventHandler);
134
+ }
135
+
136
+ emitCommand<T extends keyof CommandEventMap>(type: T, data: CommandEventMap[T]["data"]): void {
137
+ this.emit({
138
+ type,
139
+ timestamp: Date.now(),
140
+ data,
141
+ source: "command",
142
+ category: (type as string).endsWith("_response") ? "response" : "request",
143
+ intent: (type as string).endsWith("_response") ? "result" : "request",
144
+ } as BusEvent);
145
+ }
146
+
147
+ request<T extends CommandRequestType>(
148
+ type: T,
149
+ data: RequestDataFor<T>,
150
+ timeout: number = 30000
151
+ ): Promise<ResponseEventFor<T>> {
152
+ return new Promise((resolve, reject) => {
153
+ const requestId = generateRequestId();
154
+
155
+ // Get response type from request type
156
+ const responseType = (type as string).replace(
157
+ "_request",
158
+ "_response"
159
+ ) as CommandRequestResponseMap[T];
160
+
161
+ // Set up timeout
162
+ const timer = setTimeout(() => {
163
+ unsubscribe();
164
+ reject(new Error(`Request timeout: ${type}`));
165
+ }, timeout);
166
+
167
+ // Listen for response
168
+ const unsubscribe = this.onCommand(responseType, (event) => {
169
+ // Match by requestId
170
+ if ((event.data as { requestId: string }).requestId === requestId) {
171
+ clearTimeout(timer);
172
+ unsubscribe();
173
+ resolve(event as ResponseEventFor<T>);
174
+ }
175
+ });
176
+
177
+ // Emit request with generated requestId
178
+ this.emitCommand(type, { ...data, requestId } as CommandEventMap[T]["data"]);
179
+ });
180
+ }
181
+
182
+ destroy(): void {
183
+ if (this.isDestroyed) return;
184
+ this.isDestroyed = true;
185
+ this.subscriptions = [];
186
+ this.subject.complete();
187
+ }
188
+
189
+ private dispatch(event: BusEvent): void {
190
+ const toRemove: number[] = [];
191
+
192
+ for (const sub of this.subscriptions) {
193
+ if (!this.matchesType(sub.type, event.type)) continue;
194
+ if (sub.filter && !sub.filter(event)) continue;
195
+
196
+ try {
197
+ sub.handler(event);
198
+ } catch (err) {
199
+ logger.error("Event handler error", {
200
+ eventType: event.type,
201
+ subscriptionType: sub.type,
202
+ error: err instanceof Error ? err.message : String(err),
203
+ stack: err instanceof Error ? err.stack : undefined,
204
+ });
205
+ }
206
+
207
+ if (sub.once) {
208
+ toRemove.push(sub.id);
209
+ }
210
+ }
211
+
212
+ for (const id of toRemove) {
213
+ this.removeSubscription(id);
214
+ }
215
+ }
216
+
217
+ private matchesType(subscriptionType: string | string[] | "*", eventType: string): boolean {
218
+ if (subscriptionType === "*") return true;
219
+ if (Array.isArray(subscriptionType)) return subscriptionType.includes(eventType);
220
+ return subscriptionType === eventType;
221
+ }
222
+
223
+ private sortByPriority(): void {
224
+ this.subscriptions.sort((a, b) => b.priority - a.priority);
225
+ }
226
+
227
+ private removeSubscription(id: number): void {
228
+ this.subscriptions = this.subscriptions.filter((s) => s.id !== id);
229
+ }
230
+
231
+ /**
232
+ * Get a read-only consumer view (only subscribe methods)
233
+ */
234
+ asConsumer(): EventConsumer {
235
+ if (!this.consumerView) {
236
+ this.consumerView = {
237
+ on: this.on.bind(this),
238
+ onAny: this.onAny.bind(this),
239
+ once: this.once.bind(this),
240
+ onCommand: this.onCommand.bind(this),
241
+ request: this.request.bind(this),
242
+ };
243
+ }
244
+ return this.consumerView;
245
+ }
246
+
247
+ /**
248
+ * Get a write-only producer view (only emit methods)
249
+ */
250
+ asProducer(): EventProducer {
251
+ if (!this.producerView) {
252
+ this.producerView = {
253
+ emit: this.emit.bind(this),
254
+ emitBatch: this.emitBatch.bind(this),
255
+ emitCommand: this.emitCommand.bind(this),
256
+ };
257
+ }
258
+ return this.producerView;
259
+ }
260
+ }