@agentxjs/claude-driver 1.9.5-dev → 1.9.7-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.
package/dist/index.js ADDED
@@ -0,0 +1,994 @@
1
+ // src/ClaudeDriver.ts
2
+ import { Subject as Subject2 } from "rxjs";
3
+
4
+ // ../../node_modules/commonxjs/dist/logger/index.js
5
+ var ConsoleLogger = class _ConsoleLogger {
6
+ name;
7
+ level;
8
+ colors;
9
+ timestamps;
10
+ static COLORS = {
11
+ DEBUG: "\x1B[36m",
12
+ INFO: "\x1B[32m",
13
+ WARN: "\x1B[33m",
14
+ ERROR: "\x1B[31m",
15
+ RESET: "\x1B[0m"
16
+ };
17
+ constructor(name, options = {}) {
18
+ this.name = name;
19
+ this.level = options.level ?? "info";
20
+ this.colors = options.colors ?? this.isNodeEnvironment();
21
+ this.timestamps = options.timestamps ?? true;
22
+ }
23
+ debug(message, context) {
24
+ if (this.isDebugEnabled()) {
25
+ this.log("DEBUG", message, context);
26
+ }
27
+ }
28
+ info(message, context) {
29
+ if (this.isInfoEnabled()) {
30
+ this.log("INFO", message, context);
31
+ }
32
+ }
33
+ warn(message, context) {
34
+ if (this.isWarnEnabled()) {
35
+ this.log("WARN", message, context);
36
+ }
37
+ }
38
+ error(message, context) {
39
+ if (this.isErrorEnabled()) {
40
+ if (message instanceof Error) {
41
+ this.log("ERROR", message.message, { ...context, stack: message.stack });
42
+ } else {
43
+ this.log("ERROR", message, context);
44
+ }
45
+ }
46
+ }
47
+ isDebugEnabled() {
48
+ return this.getLevelValue(this.level) <= this.getLevelValue("debug");
49
+ }
50
+ isInfoEnabled() {
51
+ return this.getLevelValue(this.level) <= this.getLevelValue("info");
52
+ }
53
+ isWarnEnabled() {
54
+ return this.getLevelValue(this.level) <= this.getLevelValue("warn");
55
+ }
56
+ isErrorEnabled() {
57
+ return this.getLevelValue(this.level) <= this.getLevelValue("error");
58
+ }
59
+ getLevelValue(level) {
60
+ const levels = {
61
+ debug: 0,
62
+ info: 1,
63
+ warn: 2,
64
+ error: 3,
65
+ silent: 4
66
+ };
67
+ return levels[level];
68
+ }
69
+ log(level, message, context) {
70
+ const parts = [];
71
+ if (this.timestamps) {
72
+ parts.push((/* @__PURE__ */ new Date()).toISOString());
73
+ }
74
+ if (this.colors) {
75
+ const color = _ConsoleLogger.COLORS[level];
76
+ parts.push(`${color}${level.padEnd(5)}${_ConsoleLogger.COLORS.RESET}`);
77
+ } else {
78
+ parts.push(level.padEnd(5));
79
+ }
80
+ parts.push(`[${this.name}]`);
81
+ parts.push(message);
82
+ const logLine = parts.join(" ");
83
+ const consoleMethod = this.getConsoleMethod(level);
84
+ if (context && Object.keys(context).length > 0) {
85
+ consoleMethod(logLine, context);
86
+ } else {
87
+ consoleMethod(logLine);
88
+ }
89
+ }
90
+ getConsoleMethod(level) {
91
+ switch (level) {
92
+ case "DEBUG":
93
+ return console.debug.bind(console);
94
+ case "INFO":
95
+ return console.info.bind(console);
96
+ case "WARN":
97
+ return console.warn.bind(console);
98
+ case "ERROR":
99
+ return console.error.bind(console);
100
+ default:
101
+ return console.log.bind(console);
102
+ }
103
+ }
104
+ isNodeEnvironment() {
105
+ return typeof process !== "undefined" && process.versions?.node !== void 0;
106
+ }
107
+ };
108
+ var externalFactory = null;
109
+ var factoryVersion = 0;
110
+ var LoggerFactoryImpl = class {
111
+ static loggers = /* @__PURE__ */ new Map();
112
+ static config = {
113
+ defaultLevel: "info"
114
+ };
115
+ static getLogger(nameOrClass) {
116
+ const name = typeof nameOrClass === "string" ? nameOrClass : nameOrClass.name;
117
+ if (this.loggers.has(name)) {
118
+ return this.loggers.get(name);
119
+ }
120
+ const lazyLogger = this.createLazyLogger(name);
121
+ this.loggers.set(name, lazyLogger);
122
+ return lazyLogger;
123
+ }
124
+ static configure(config) {
125
+ this.config = { ...this.config, ...config };
126
+ }
127
+ static reset() {
128
+ this.loggers.clear();
129
+ this.config = { defaultLevel: "info" };
130
+ externalFactory = null;
131
+ factoryVersion++;
132
+ }
133
+ static createLazyLogger(name) {
134
+ let realLogger = null;
135
+ let loggerVersion = -1;
136
+ const getRealLogger = () => {
137
+ if (!realLogger || loggerVersion !== factoryVersion) {
138
+ realLogger = this.createLogger(name);
139
+ loggerVersion = factoryVersion;
140
+ }
141
+ return realLogger;
142
+ };
143
+ return {
144
+ name,
145
+ level: this.config.defaultLevel || "info",
146
+ debug: (message, context) => getRealLogger().debug(message, context),
147
+ info: (message, context) => getRealLogger().info(message, context),
148
+ warn: (message, context) => getRealLogger().warn(message, context),
149
+ error: (message, context) => getRealLogger().error(message, context),
150
+ isDebugEnabled: () => getRealLogger().isDebugEnabled(),
151
+ isInfoEnabled: () => getRealLogger().isInfoEnabled(),
152
+ isWarnEnabled: () => getRealLogger().isWarnEnabled(),
153
+ isErrorEnabled: () => getRealLogger().isErrorEnabled()
154
+ };
155
+ }
156
+ static createLogger(name) {
157
+ if (externalFactory) {
158
+ return externalFactory.getLogger(name);
159
+ }
160
+ if (this.config.defaultImplementation) {
161
+ return this.config.defaultImplementation(name);
162
+ }
163
+ return new ConsoleLogger(name, {
164
+ level: this.config.defaultLevel,
165
+ ...this.config.consoleOptions
166
+ });
167
+ }
168
+ };
169
+ function createLogger(name) {
170
+ return LoggerFactoryImpl.getLogger(name);
171
+ }
172
+
173
+ // src/helpers.ts
174
+ function isTextPart(part) {
175
+ return part.type === "text";
176
+ }
177
+ function isImagePart(part) {
178
+ return part.type === "image";
179
+ }
180
+ function isFilePart(part) {
181
+ return part.type === "file";
182
+ }
183
+ function buildSDKContent(message) {
184
+ if (typeof message.content === "string") {
185
+ return message.content;
186
+ }
187
+ if (!Array.isArray(message.content)) {
188
+ return "";
189
+ }
190
+ const parts = message.content;
191
+ const hasNonTextParts = parts.some((p) => !isTextPart(p));
192
+ if (!hasNonTextParts) {
193
+ return parts.filter(isTextPart).map((p) => p.text).join("\n");
194
+ }
195
+ return parts.map((part) => {
196
+ if (isTextPart(part)) {
197
+ return {
198
+ type: "text",
199
+ text: part.text
200
+ };
201
+ }
202
+ if (isImagePart(part)) {
203
+ return {
204
+ type: "image",
205
+ source: {
206
+ type: "base64",
207
+ media_type: part.mediaType,
208
+ data: part.data
209
+ }
210
+ };
211
+ }
212
+ if (isFilePart(part)) {
213
+ return {
214
+ type: "document",
215
+ source: {
216
+ type: "base64",
217
+ media_type: part.mediaType,
218
+ data: part.data
219
+ }
220
+ };
221
+ }
222
+ return { type: "text", text: "" };
223
+ });
224
+ }
225
+ function buildSDKUserMessage(message, sessionId) {
226
+ return {
227
+ type: "user",
228
+ message: { role: "user", content: buildSDKContent(message) },
229
+ parent_tool_use_id: null,
230
+ session_id: sessionId
231
+ };
232
+ }
233
+
234
+ // src/SDKQueryLifecycle.ts
235
+ import {
236
+ query
237
+ } from "@anthropic-ai/claude-agent-sdk";
238
+ import { Subject } from "rxjs";
239
+
240
+ // src/buildOptions.ts
241
+ var logger = createLogger("claude-driver/buildOptions");
242
+ function buildOptions(context, abortController) {
243
+ const options = {
244
+ abortController,
245
+ includePartialMessages: true
246
+ };
247
+ if (context.cwd) {
248
+ options.cwd = context.cwd;
249
+ }
250
+ const env = {};
251
+ for (const [key, value] of Object.entries(process.env)) {
252
+ if (value !== void 0) {
253
+ env[key] = value;
254
+ }
255
+ }
256
+ if (!env.PATH && process.env.PATH) {
257
+ env.PATH = process.env.PATH;
258
+ }
259
+ env.AGENTX_ENVIRONMENT = "true";
260
+ if (context.baseUrl) {
261
+ env.ANTHROPIC_BASE_URL = context.baseUrl;
262
+ }
263
+ if (context.apiKey) {
264
+ env.ANTHROPIC_API_KEY = context.apiKey;
265
+ }
266
+ options.env = env;
267
+ logger.info("buildOptions called", {
268
+ hasPath: !!env.PATH,
269
+ pathLength: env.PATH?.length,
270
+ hasApiKey: !!env.ANTHROPIC_API_KEY,
271
+ hasBaseUrl: !!env.ANTHROPIC_BASE_URL,
272
+ baseUrl: env.ANTHROPIC_BASE_URL,
273
+ model: context.model,
274
+ permissionMode: context.permissionMode || "bypassPermissions",
275
+ cwd: context.cwd,
276
+ systemPrompt: context.systemPrompt,
277
+ mcpServers: context.mcpServers ? Object.keys(context.mcpServers) : []
278
+ });
279
+ options.stderr = (data) => {
280
+ logger.info("SDK stderr", { data: data.trim() });
281
+ };
282
+ if (context.claudeCodePath) {
283
+ options.pathToClaudeCodeExecutable = context.claudeCodePath;
284
+ logger.info("Claude Code path configured", { path: context.claudeCodePath });
285
+ }
286
+ if (context.model) options.model = context.model;
287
+ if (context.systemPrompt) options.systemPrompt = context.systemPrompt;
288
+ if (context.maxTurns) options.maxTurns = context.maxTurns;
289
+ if (context.maxThinkingTokens) options.maxThinkingTokens = context.maxThinkingTokens;
290
+ if (context.resume) options.resume = context.resume;
291
+ if (context.mcpServers) {
292
+ options.mcpServers = context.mcpServers;
293
+ logger.info("MCP servers configured", {
294
+ serverNames: Object.keys(context.mcpServers)
295
+ });
296
+ }
297
+ if (context.permissionMode) {
298
+ options.permissionMode = context.permissionMode;
299
+ if (context.permissionMode === "bypassPermissions") {
300
+ options.allowDangerouslySkipPermissions = true;
301
+ }
302
+ } else {
303
+ options.permissionMode = "bypassPermissions";
304
+ options.allowDangerouslySkipPermissions = true;
305
+ }
306
+ logger.info("SDK Options built", {
307
+ model: options.model,
308
+ systemPrompt: options.systemPrompt,
309
+ permissionMode: options.permissionMode,
310
+ cwd: options.cwd,
311
+ resume: options.resume,
312
+ maxTurns: options.maxTurns,
313
+ mcpServers: options.mcpServers ? Object.keys(options.mcpServers) : []
314
+ });
315
+ return options;
316
+ }
317
+
318
+ // src/observableToAsyncIterable.ts
319
+ async function* observableToAsyncIterable(observable) {
320
+ const queue = [];
321
+ let resolve = null;
322
+ let reject = null;
323
+ let done = false;
324
+ let error = null;
325
+ const subscription = observable.subscribe({
326
+ next: (value) => {
327
+ if (resolve) {
328
+ resolve({ value, done: false });
329
+ resolve = null;
330
+ reject = null;
331
+ } else {
332
+ queue.push(value);
333
+ }
334
+ },
335
+ error: (err) => {
336
+ error = err instanceof Error ? err : new Error(String(err));
337
+ done = true;
338
+ if (reject) {
339
+ reject(error);
340
+ resolve = null;
341
+ reject = null;
342
+ }
343
+ },
344
+ complete: () => {
345
+ done = true;
346
+ if (resolve) {
347
+ resolve({ value: void 0, done: true });
348
+ resolve = null;
349
+ reject = null;
350
+ }
351
+ }
352
+ });
353
+ try {
354
+ while (!done || queue.length > 0) {
355
+ if (error) {
356
+ throw error;
357
+ }
358
+ if (queue.length > 0) {
359
+ yield queue.shift();
360
+ } else if (!done) {
361
+ const result = await new Promise((res, rej) => {
362
+ resolve = (iterResult) => {
363
+ if (iterResult.done) {
364
+ done = true;
365
+ res({ done: true });
366
+ } else {
367
+ res({ value: iterResult.value, done: false });
368
+ }
369
+ };
370
+ reject = rej;
371
+ });
372
+ if (!result.done) {
373
+ yield result.value;
374
+ }
375
+ }
376
+ }
377
+ } finally {
378
+ subscription.unsubscribe();
379
+ }
380
+ }
381
+
382
+ // src/SDKQueryLifecycle.ts
383
+ var logger2 = createLogger("claude-driver/SDKQueryLifecycle");
384
+ var SDKQueryLifecycle = class {
385
+ config;
386
+ _callbacks;
387
+ promptSubject = new Subject();
388
+ claudeQuery = null;
389
+ isInitialized = false;
390
+ abortController = null;
391
+ capturedSessionId = null;
392
+ constructor(config, callbacks = {}) {
393
+ this.config = config;
394
+ this._callbacks = callbacks;
395
+ }
396
+ /**
397
+ * Get current callbacks (for reading/modification)
398
+ */
399
+ get callbacks() {
400
+ return this._callbacks;
401
+ }
402
+ /**
403
+ * Update callbacks
404
+ *
405
+ * Allows changing callbacks after initialization.
406
+ * Useful for per-turn callback setup.
407
+ */
408
+ setCallbacks(callbacks) {
409
+ this._callbacks = { ...this._callbacks, ...callbacks };
410
+ }
411
+ /**
412
+ * Check if the query is initialized
413
+ */
414
+ get initialized() {
415
+ return this.isInitialized;
416
+ }
417
+ /**
418
+ * Warmup the SDK query (pre-initialize)
419
+ *
420
+ * Call this early to start the SDK subprocess before the first message.
421
+ * This reduces latency for the first user message.
422
+ *
423
+ * @returns Promise that resolves when SDK is ready
424
+ */
425
+ async warmup() {
426
+ logger2.info("Warming up SDKQueryLifecycle");
427
+ await this.initialize();
428
+ logger2.info("SDKQueryLifecycle warmup complete");
429
+ }
430
+ /**
431
+ * Initialize the SDK query (lazy initialization)
432
+ *
433
+ * Creates the query and starts the background listener.
434
+ * Safe to call multiple times - will only initialize once.
435
+ */
436
+ async initialize() {
437
+ if (this.isInitialized) return;
438
+ logger2.info("Initializing SDKQueryLifecycle");
439
+ this.abortController = new AbortController();
440
+ const context = {
441
+ apiKey: this.config.apiKey,
442
+ baseUrl: this.config.baseUrl,
443
+ model: this.config.model,
444
+ systemPrompt: this.config.systemPrompt,
445
+ cwd: this.config.cwd,
446
+ resume: this.config.resumeSessionId,
447
+ mcpServers: this.config.mcpServers,
448
+ claudeCodePath: this.config.claudeCodePath
449
+ };
450
+ const sdkOptions = buildOptions(context, this.abortController);
451
+ const promptStream = observableToAsyncIterable(this.promptSubject);
452
+ this.claudeQuery = query({
453
+ prompt: promptStream,
454
+ options: sdkOptions
455
+ });
456
+ this.isInitialized = true;
457
+ this.startBackgroundListener();
458
+ logger2.info("SDKQueryLifecycle initialized");
459
+ }
460
+ /**
461
+ * Send a message to the SDK
462
+ *
463
+ * Must call initialize() first.
464
+ */
465
+ send(message) {
466
+ if (!this.isInitialized) {
467
+ throw new Error("SDKQueryLifecycle not initialized. Call initialize() first.");
468
+ }
469
+ this.promptSubject.next(message);
470
+ }
471
+ /**
472
+ * Interrupt the current SDK operation
473
+ */
474
+ interrupt() {
475
+ if (this.claudeQuery) {
476
+ logger2.debug("Interrupting SDK query");
477
+ this.claudeQuery.interrupt().catch((err) => {
478
+ logger2.debug("SDK interrupt() error (may be expected)", { error: err });
479
+ });
480
+ }
481
+ }
482
+ /**
483
+ * Reset state and cleanup resources
484
+ *
485
+ * This properly terminates the Claude subprocess by:
486
+ * 1. Completing the prompt stream (signals end of input)
487
+ * 2. Interrupting any ongoing operation
488
+ * 3. Resetting state for potential reuse
489
+ */
490
+ reset() {
491
+ logger2.debug("Resetting SDKQueryLifecycle");
492
+ this.promptSubject.complete();
493
+ if (this.claudeQuery) {
494
+ this.claudeQuery.interrupt().catch((err) => {
495
+ logger2.debug("SDK interrupt() during reset (may be expected)", { error: err });
496
+ });
497
+ }
498
+ this.isInitialized = false;
499
+ this.claudeQuery = null;
500
+ this.abortController = null;
501
+ this.promptSubject = new Subject();
502
+ }
503
+ /**
504
+ * Dispose and cleanup all resources
505
+ *
506
+ * Should be called when the lifecycle is no longer needed.
507
+ */
508
+ dispose() {
509
+ logger2.debug("Disposing SDKQueryLifecycle");
510
+ if (this.claudeQuery) {
511
+ this.claudeQuery.interrupt().catch((err) => {
512
+ logger2.debug("SDK interrupt() during dispose (may be expected)", { error: err });
513
+ });
514
+ }
515
+ if (this.abortController) {
516
+ this.abortController.abort();
517
+ }
518
+ this.reset();
519
+ logger2.debug("SDKQueryLifecycle disposed");
520
+ }
521
+ /**
522
+ * Start the background listener for SDK responses
523
+ */
524
+ startBackgroundListener() {
525
+ (async () => {
526
+ try {
527
+ for await (const sdkMsg of this.claudeQuery) {
528
+ logger2.debug("SDK message received", {
529
+ type: sdkMsg.type,
530
+ subtype: sdkMsg.subtype,
531
+ sessionId: sdkMsg.session_id
532
+ });
533
+ if (sdkMsg.session_id && this._callbacks.onSessionIdCaptured && this.capturedSessionId !== sdkMsg.session_id) {
534
+ this.capturedSessionId = sdkMsg.session_id;
535
+ this._callbacks.onSessionIdCaptured(sdkMsg.session_id);
536
+ }
537
+ if (sdkMsg.type === "stream_event") {
538
+ this._callbacks.onStreamEvent?.(sdkMsg);
539
+ }
540
+ if (sdkMsg.type === "user") {
541
+ this._callbacks.onUserMessage?.(sdkMsg);
542
+ }
543
+ if (sdkMsg.type === "result") {
544
+ logger2.info("SDK result received", {
545
+ subtype: sdkMsg.subtype,
546
+ is_error: sdkMsg.is_error
547
+ });
548
+ this._callbacks.onResult?.(sdkMsg);
549
+ }
550
+ }
551
+ this._callbacks.onListenerExit?.("normal");
552
+ } catch (error) {
553
+ if (this.isAbortError(error)) {
554
+ logger2.debug("Background listener aborted (expected during interrupt)");
555
+ this._callbacks.onListenerExit?.("abort");
556
+ } else {
557
+ logger2.error("Background listener error", { error });
558
+ this._callbacks.onError?.(error instanceof Error ? error : new Error(String(error)));
559
+ this._callbacks.onListenerExit?.("error");
560
+ }
561
+ this.reset();
562
+ }
563
+ })();
564
+ }
565
+ /**
566
+ * Check if an error is an abort error
567
+ */
568
+ isAbortError(error) {
569
+ if (error instanceof Error) {
570
+ if (error.name === "AbortError") return true;
571
+ if (error.message.includes("aborted")) return true;
572
+ if (error.message.includes("abort")) return true;
573
+ }
574
+ return false;
575
+ }
576
+ };
577
+
578
+ // src/ClaudeDriver.ts
579
+ var logger3 = createLogger("claude-driver/ClaudeDriver");
580
+ var ClaudeDriver = class {
581
+ name = "ClaudeDriver";
582
+ _sessionId = null;
583
+ _state = "idle";
584
+ config;
585
+ queryLifecycle = null;
586
+ // For interrupt handling
587
+ currentTurnSubject = null;
588
+ constructor(config) {
589
+ this.config = config;
590
+ }
591
+ // ============================================================================
592
+ // Driver Interface Properties
593
+ // ============================================================================
594
+ get sessionId() {
595
+ return this._sessionId;
596
+ }
597
+ get state() {
598
+ return this._state;
599
+ }
600
+ // ============================================================================
601
+ // Lifecycle Methods
602
+ // ============================================================================
603
+ /**
604
+ * Initialize the Driver
605
+ *
606
+ * Starts SDK subprocess and MCP servers.
607
+ * Must be called before receive().
608
+ */
609
+ async initialize() {
610
+ if (this._state !== "idle") {
611
+ throw new Error(`Cannot initialize: Driver is in "${this._state}" state`);
612
+ }
613
+ logger3.info("Initializing ClaudeDriver", { agentId: this.config.agentId });
614
+ logger3.info("ClaudeDriver initialized");
615
+ }
616
+ /**
617
+ * Dispose and cleanup resources
618
+ *
619
+ * Stops SDK subprocess and MCP servers.
620
+ * Driver cannot be used after dispose().
621
+ */
622
+ async dispose() {
623
+ if (this._state === "disposed") {
624
+ return;
625
+ }
626
+ logger3.info("Disposing ClaudeDriver", { agentId: this.config.agentId });
627
+ if (this.currentTurnSubject) {
628
+ this.currentTurnSubject.complete();
629
+ this.currentTurnSubject = null;
630
+ }
631
+ if (this.queryLifecycle) {
632
+ this.queryLifecycle.dispose();
633
+ this.queryLifecycle = null;
634
+ }
635
+ this._state = "disposed";
636
+ logger3.info("ClaudeDriver disposed");
637
+ }
638
+ // ============================================================================
639
+ // Core Methods
640
+ // ============================================================================
641
+ /**
642
+ * Receive a user message and return stream of events
643
+ *
644
+ * This is the main method for communication.
645
+ * Returns an AsyncIterable that yields DriverStreamEvent.
646
+ *
647
+ * @param message - User message to send
648
+ * @returns AsyncIterable of stream events
649
+ */
650
+ async *receive(message) {
651
+ if (this._state === "disposed") {
652
+ throw new Error("Cannot receive: Driver is disposed");
653
+ }
654
+ if (this._state === "active") {
655
+ throw new Error("Cannot receive: Driver is already processing a message");
656
+ }
657
+ this._state = "active";
658
+ try {
659
+ await this.ensureLifecycle();
660
+ const turnSubject = new Subject2();
661
+ this.currentTurnSubject = turnSubject;
662
+ let isComplete = false;
663
+ let turnError = null;
664
+ this.setupTurnCallbacks(turnSubject, () => {
665
+ isComplete = true;
666
+ }, (error) => {
667
+ turnError = error;
668
+ isComplete = true;
669
+ });
670
+ const sessionId = this._sessionId || "default";
671
+ const sdkMessage = buildSDKUserMessage(message, sessionId);
672
+ logger3.debug("Sending message to Claude", {
673
+ content: typeof message.content === "string" ? message.content.substring(0, 80) : "[structured]",
674
+ agentId: this.config.agentId
675
+ });
676
+ this.queryLifecycle.send(sdkMessage);
677
+ yield* this.yieldFromSubject(turnSubject, () => isComplete, () => turnError);
678
+ } finally {
679
+ this._state = "idle";
680
+ this.currentTurnSubject = null;
681
+ }
682
+ }
683
+ /**
684
+ * Interrupt current operation
685
+ *
686
+ * Stops the current receive() operation gracefully.
687
+ * The AsyncIterable will emit an "interrupted" event and complete.
688
+ */
689
+ interrupt() {
690
+ if (this._state !== "active") {
691
+ logger3.debug("Interrupt called but no active operation");
692
+ return;
693
+ }
694
+ logger3.debug("Interrupting ClaudeDriver");
695
+ if (this.currentTurnSubject) {
696
+ this.currentTurnSubject.next({
697
+ type: "interrupted",
698
+ timestamp: Date.now(),
699
+ data: { reason: "user" }
700
+ });
701
+ this.currentTurnSubject.complete();
702
+ }
703
+ if (this.queryLifecycle) {
704
+ this.queryLifecycle.interrupt();
705
+ }
706
+ }
707
+ // ============================================================================
708
+ // Private Methods
709
+ // ============================================================================
710
+ /**
711
+ * Ensure SDK lifecycle is initialized
712
+ */
713
+ async ensureLifecycle() {
714
+ if (this.queryLifecycle && this.queryLifecycle.initialized) {
715
+ return;
716
+ }
717
+ this.queryLifecycle = new SDKQueryLifecycle(
718
+ {
719
+ apiKey: this.config.apiKey,
720
+ baseUrl: this.config.baseUrl,
721
+ model: this.config.model,
722
+ systemPrompt: this.config.systemPrompt,
723
+ cwd: this.config.cwd,
724
+ resumeSessionId: this.config.resumeSessionId,
725
+ mcpServers: this.config.mcpServers
726
+ },
727
+ {
728
+ onSessionIdCaptured: (sessionId) => {
729
+ this._sessionId = sessionId;
730
+ this.config.onSessionIdCaptured?.(sessionId);
731
+ }
732
+ }
733
+ );
734
+ await this.queryLifecycle.initialize();
735
+ }
736
+ /**
737
+ * Setup callbacks for a single turn
738
+ */
739
+ setupTurnCallbacks(subject, onComplete, onError) {
740
+ if (!this.queryLifecycle) return;
741
+ const blockContext = {
742
+ currentBlockType: null,
743
+ currentToolId: null,
744
+ currentToolName: null,
745
+ lastStopReason: null,
746
+ accumulatedToolInput: ""
747
+ };
748
+ this.queryLifecycle.setCallbacks({
749
+ onStreamEvent: (msg) => {
750
+ const event = this.convertStreamEvent(msg, blockContext);
751
+ if (event) {
752
+ subject.next(event);
753
+ }
754
+ },
755
+ onUserMessage: (msg) => {
756
+ const events = this.convertUserMessage(msg);
757
+ for (const event of events) {
758
+ subject.next(event);
759
+ }
760
+ },
761
+ onResult: (msg) => {
762
+ const resultMsg = msg;
763
+ if (resultMsg.is_error) {
764
+ subject.next({
765
+ type: "error",
766
+ timestamp: Date.now(),
767
+ data: {
768
+ message: resultMsg.error?.message || "Unknown error",
769
+ errorCode: "sdk_error"
770
+ }
771
+ });
772
+ }
773
+ subject.complete();
774
+ onComplete();
775
+ },
776
+ onError: (error) => {
777
+ subject.next({
778
+ type: "error",
779
+ timestamp: Date.now(),
780
+ data: {
781
+ message: error.message,
782
+ errorCode: "runtime_error"
783
+ }
784
+ });
785
+ subject.complete();
786
+ onError(error);
787
+ }
788
+ });
789
+ }
790
+ /**
791
+ * Convert SDK stream_event to DriverStreamEvent
792
+ */
793
+ convertStreamEvent(sdkMsg, blockContext) {
794
+ const event = sdkMsg.event;
795
+ const timestamp = Date.now();
796
+ switch (event.type) {
797
+ case "message_start":
798
+ return {
799
+ type: "message_start",
800
+ timestamp,
801
+ data: {
802
+ messageId: event.message.id,
803
+ model: event.message.model
804
+ }
805
+ };
806
+ case "content_block_start": {
807
+ const contentBlock = event.content_block;
808
+ if (contentBlock.type === "text") {
809
+ blockContext.currentBlockType = "text";
810
+ return null;
811
+ } else if (contentBlock.type === "tool_use") {
812
+ blockContext.currentBlockType = "tool_use";
813
+ blockContext.currentToolId = contentBlock.id || null;
814
+ blockContext.currentToolName = contentBlock.name || null;
815
+ blockContext.accumulatedToolInput = "";
816
+ return {
817
+ type: "tool_use_start",
818
+ timestamp,
819
+ data: {
820
+ toolCallId: contentBlock.id || "",
821
+ toolName: contentBlock.name || ""
822
+ }
823
+ };
824
+ }
825
+ return null;
826
+ }
827
+ case "content_block_delta": {
828
+ const delta = event.delta;
829
+ if (delta.type === "text_delta") {
830
+ return {
831
+ type: "text_delta",
832
+ timestamp,
833
+ data: { text: delta.text || "" }
834
+ };
835
+ } else if (delta.type === "input_json_delta") {
836
+ blockContext.accumulatedToolInput += delta.partial_json || "";
837
+ return {
838
+ type: "input_json_delta",
839
+ timestamp,
840
+ data: { partialJson: delta.partial_json || "" }
841
+ };
842
+ }
843
+ return null;
844
+ }
845
+ case "content_block_stop":
846
+ if (blockContext.currentBlockType === "tool_use" && blockContext.currentToolId) {
847
+ let input = {};
848
+ try {
849
+ if (blockContext.accumulatedToolInput) {
850
+ input = JSON.parse(blockContext.accumulatedToolInput);
851
+ }
852
+ } catch {
853
+ logger3.warn("Failed to parse tool input JSON", {
854
+ input: blockContext.accumulatedToolInput
855
+ });
856
+ }
857
+ const event2 = {
858
+ type: "tool_use_stop",
859
+ timestamp,
860
+ data: {
861
+ toolCallId: blockContext.currentToolId,
862
+ toolName: blockContext.currentToolName || "",
863
+ input
864
+ }
865
+ };
866
+ blockContext.currentBlockType = null;
867
+ blockContext.currentToolId = null;
868
+ blockContext.currentToolName = null;
869
+ blockContext.accumulatedToolInput = "";
870
+ return event2;
871
+ }
872
+ blockContext.currentBlockType = null;
873
+ return null;
874
+ case "message_delta": {
875
+ const msgDelta = event.delta;
876
+ if (msgDelta.stop_reason) {
877
+ blockContext.lastStopReason = msgDelta.stop_reason;
878
+ }
879
+ return null;
880
+ }
881
+ case "message_stop":
882
+ return {
883
+ type: "message_stop",
884
+ timestamp,
885
+ data: {
886
+ stopReason: this.mapStopReason(blockContext.lastStopReason)
887
+ }
888
+ };
889
+ default:
890
+ return null;
891
+ }
892
+ }
893
+ /**
894
+ * Convert SDK user message (contains tool_result)
895
+ */
896
+ convertUserMessage(msg) {
897
+ const events = [];
898
+ const sdkMsg = msg;
899
+ if (!sdkMsg.message || !Array.isArray(sdkMsg.message.content)) {
900
+ return events;
901
+ }
902
+ for (const block of sdkMsg.message.content) {
903
+ if (block && typeof block === "object" && "type" in block && block.type === "tool_result") {
904
+ const toolResultBlock = block;
905
+ events.push({
906
+ type: "tool_result",
907
+ timestamp: Date.now(),
908
+ data: {
909
+ toolCallId: toolResultBlock.tool_use_id,
910
+ result: toolResultBlock.content,
911
+ isError: toolResultBlock.is_error
912
+ }
913
+ });
914
+ }
915
+ }
916
+ return events;
917
+ }
918
+ /**
919
+ * Map SDK stop reason to our StopReason type
920
+ */
921
+ mapStopReason(sdkReason) {
922
+ switch (sdkReason) {
923
+ case "end_turn":
924
+ return "end_turn";
925
+ case "max_tokens":
926
+ return "max_tokens";
927
+ case "tool_use":
928
+ return "tool_use";
929
+ case "stop_sequence":
930
+ return "stop_sequence";
931
+ default:
932
+ return "other";
933
+ }
934
+ }
935
+ /**
936
+ * Yield events from Subject as AsyncIterable
937
+ */
938
+ async *yieldFromSubject(subject, _isComplete, getError) {
939
+ const queue = [];
940
+ let resolve = null;
941
+ let done = false;
942
+ const subscription = subject.subscribe({
943
+ next: (value) => {
944
+ if (resolve) {
945
+ resolve({ value, done: false });
946
+ resolve = null;
947
+ } else {
948
+ queue.push(value);
949
+ }
950
+ },
951
+ complete: () => {
952
+ done = true;
953
+ if (resolve) {
954
+ resolve({ value: void 0, done: true });
955
+ resolve = null;
956
+ }
957
+ },
958
+ error: (_err) => {
959
+ done = true;
960
+ }
961
+ });
962
+ try {
963
+ while (!done || queue.length > 0) {
964
+ const error = getError();
965
+ if (error) {
966
+ throw error;
967
+ }
968
+ if (queue.length > 0) {
969
+ yield queue.shift();
970
+ } else if (!done) {
971
+ const result = await new Promise((res) => {
972
+ resolve = res;
973
+ });
974
+ if (!result.done) {
975
+ yield result.value;
976
+ }
977
+ }
978
+ }
979
+ } finally {
980
+ subscription.unsubscribe();
981
+ }
982
+ }
983
+ };
984
+ function createClaudeDriver(config) {
985
+ return new ClaudeDriver(config);
986
+ }
987
+ export {
988
+ ClaudeDriver,
989
+ SDKQueryLifecycle,
990
+ buildSDKContent,
991
+ buildSDKUserMessage,
992
+ createClaudeDriver
993
+ };
994
+ //# sourceMappingURL=index.js.map