@agentxjs/claude-driver 1.9.3-dev → 1.9.6-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.d.ts +302 -0
- package/dist/index.js +994 -0
- package/dist/index.js.map +1 -0
- package/package.json +14 -3
- package/tsconfig.json +0 -10
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
|