@wu529778790/open-im 1.7.0-beta.2 → 1.7.1-beta.0
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/adapters/claude-adapter.d.ts +26 -0
- package/dist/adapters/claude-adapter.js +76 -0
- package/dist/adapters/claude-sdk-adapter.d.ts +7 -7
- package/dist/adapters/claude-sdk-adapter.js +101 -126
- package/dist/adapters/registry.js +13 -2
- package/dist/claude/cli-runner.d.ts +29 -0
- package/dist/claude/cli-runner.js +231 -0
- package/dist/claude/process-pool.d.ts +84 -0
- package/dist/claude/process-pool.js +312 -0
- package/dist/commands/handler.js +3 -5
- package/dist/config-web-page-script.js +4 -0
- package/dist/config-web-page-template.js +4 -0
- package/dist/config-web.js +6 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +54 -2
- package/dist/setup.js +1 -1
- package/dist/shared/ai-task.js +13 -20
- package/dist/shared/ai-task.test.js +1 -15
- package/package.json +1 -1
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ToolAdapter, RunCallbacks, RunOptions, RunHandle } from './tool-adapter.interface.js';
|
|
2
|
+
export declare class ClaudeAdapter implements ToolAdapter {
|
|
3
|
+
private cliPath;
|
|
4
|
+
readonly toolId = "claude";
|
|
5
|
+
constructor(cliPath: string, adapterOptions?: {
|
|
6
|
+
useProcessPool?: boolean;
|
|
7
|
+
idleTimeoutMs?: number;
|
|
8
|
+
});
|
|
9
|
+
run(prompt: string, sessionId: string | undefined, workDir: string, callbacks: RunCallbacks, options?: RunOptions): RunHandle;
|
|
10
|
+
/**
|
|
11
|
+
* Get the number of cached entries in the pool.
|
|
12
|
+
*/
|
|
13
|
+
static getCacheSize(): number;
|
|
14
|
+
/**
|
|
15
|
+
* Get the number of active processes in the pool.
|
|
16
|
+
*/
|
|
17
|
+
static getActiveProcessCount(): number;
|
|
18
|
+
/**
|
|
19
|
+
* Terminate all cached entries and processes.
|
|
20
|
+
*/
|
|
21
|
+
static terminateAll(): void;
|
|
22
|
+
/**
|
|
23
|
+
* Destroy the process pool and cleanup resources.
|
|
24
|
+
*/
|
|
25
|
+
static destroy(): void;
|
|
26
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { runClaude } from '../claude/cli-runner.js';
|
|
2
|
+
import { ClaudeProcessPool } from '../claude/process-pool.js';
|
|
3
|
+
// Global process pool instance
|
|
4
|
+
let processPool = null;
|
|
5
|
+
export class ClaudeAdapter {
|
|
6
|
+
cliPath;
|
|
7
|
+
toolId = 'claude';
|
|
8
|
+
constructor(cliPath, adapterOptions) {
|
|
9
|
+
this.cliPath = cliPath;
|
|
10
|
+
const useProcessPool = adapterOptions?.useProcessPool ?? true;
|
|
11
|
+
const idleTimeoutMs = adapterOptions?.idleTimeoutMs ?? 2 * 60 * 1000; // 2 minutes default
|
|
12
|
+
if (useProcessPool && !processPool) {
|
|
13
|
+
// Initialize process pool with configurable idle timeout
|
|
14
|
+
processPool = new ClaudeProcessPool(idleTimeoutMs);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
run(prompt, sessionId, workDir, callbacks, options) {
|
|
18
|
+
const opts = {
|
|
19
|
+
skipPermissions: options?.skipPermissions,
|
|
20
|
+
permissionMode: options?.permissionMode,
|
|
21
|
+
timeoutMs: options?.timeoutMs,
|
|
22
|
+
model: options?.model,
|
|
23
|
+
chatId: options?.chatId,
|
|
24
|
+
hookPort: options?.hookPort,
|
|
25
|
+
};
|
|
26
|
+
// Use process pool if enabled and userId is available
|
|
27
|
+
if (processPool && opts.chatId) {
|
|
28
|
+
let aborted = false;
|
|
29
|
+
// Execute using process pool with userId from chatId
|
|
30
|
+
processPool
|
|
31
|
+
.execute(opts.chatId, sessionId, this.cliPath, prompt, workDir, callbacks, opts)
|
|
32
|
+
.catch((err) => {
|
|
33
|
+
if (!aborted && callbacks.onError) {
|
|
34
|
+
callbacks.onError(err.message);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
return {
|
|
38
|
+
abort: () => {
|
|
39
|
+
aborted = true;
|
|
40
|
+
processPool.terminate(opts.chatId, sessionId);
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
// Fall back to original implementation
|
|
45
|
+
return runClaude(this.cliPath, prompt, sessionId, workDir, callbacks, opts);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get the number of cached entries in the pool.
|
|
49
|
+
*/
|
|
50
|
+
static getCacheSize() {
|
|
51
|
+
return processPool?.size() ?? 0;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Get the number of active processes in the pool.
|
|
55
|
+
*/
|
|
56
|
+
static getActiveProcessCount() {
|
|
57
|
+
return processPool?.activeCount() ?? 0;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Terminate all cached entries and processes.
|
|
61
|
+
*/
|
|
62
|
+
static terminateAll() {
|
|
63
|
+
if (processPool) {
|
|
64
|
+
processPool.terminateAll();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Destroy the process pool and cleanup resources.
|
|
69
|
+
*/
|
|
70
|
+
static destroy() {
|
|
71
|
+
if (processPool) {
|
|
72
|
+
processPool.destroy();
|
|
73
|
+
processPool = null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Claude SDK Adapter - 使用 Agent SDK
|
|
2
|
+
* Claude SDK Adapter - 使用 Agent SDK 实现持久会话,无需每次 spawn 进程
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* 1. 进程内执行 - 无 fork/exec
|
|
6
|
-
* 2.
|
|
7
|
-
* 3. 流式输出 -
|
|
4
|
+
* 优势:
|
|
5
|
+
* 1. 进程内执行 - 无 fork/exec 开销,响应更快
|
|
6
|
+
* 2. 会话复用 - resume 保留上下文,无需重新加载历史
|
|
7
|
+
* 3. 流式输出 - includePartialMessages 支持 text_delta、thinking_delta
|
|
8
8
|
*
|
|
9
|
-
* 认证:ANTHROPIC_API_KEY 或 CLAUDE_CODE_OAUTH_TOKEN
|
|
9
|
+
* 认证:ANTHROPIC_API_KEY 或 CLAUDE_CODE_OAUTH_TOKEN(claude setup-token)
|
|
10
10
|
*/
|
|
11
11
|
import type { ToolAdapter, RunCallbacks, RunOptions, RunHandle } from './tool-adapter.interface.js';
|
|
12
12
|
export declare class ClaudeSDKAdapter implements ToolAdapter {
|
|
13
13
|
readonly toolId = "claude-sdk";
|
|
14
14
|
/**
|
|
15
|
-
* 清理所有活跃的 SDK
|
|
15
|
+
* 清理所有活跃的 SDK 查询
|
|
16
16
|
*/
|
|
17
17
|
static destroy(): void;
|
|
18
18
|
run(prompt: string, sessionId: string | undefined, workDir: string, callbacks: RunCallbacks, options?: RunOptions): RunHandle;
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Claude SDK Adapter - 使用 Agent SDK
|
|
2
|
+
* Claude SDK Adapter - 使用 Agent SDK 实现持久会话,无需每次 spawn 进程
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* 1. 进程内执行 - 无 fork/exec
|
|
6
|
-
* 2.
|
|
7
|
-
* 3. 流式输出 -
|
|
4
|
+
* 优势:
|
|
5
|
+
* 1. 进程内执行 - 无 fork/exec 开销,响应更快
|
|
6
|
+
* 2. 会话复用 - resume 保留上下文,无需重新加载历史
|
|
7
|
+
* 3. 流式输出 - includePartialMessages 支持 text_delta、thinking_delta
|
|
8
8
|
*
|
|
9
|
-
* 认证:ANTHROPIC_API_KEY 或 CLAUDE_CODE_OAUTH_TOKEN
|
|
9
|
+
* 认证:ANTHROPIC_API_KEY 或 CLAUDE_CODE_OAUTH_TOKEN(claude setup-token)
|
|
10
10
|
*/
|
|
11
|
-
import {
|
|
11
|
+
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
12
12
|
import { createLogger } from '../logger.js';
|
|
13
13
|
const log = createLogger('ClaudeSDK');
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
const activeSessions = new Map();
|
|
17
|
-
// 存储正在进行的流式迭代器,用于中断
|
|
18
|
-
const activeStreams = new Set();
|
|
14
|
+
// 存储所有活跃的查询,用于清理
|
|
15
|
+
const activeQueries = new Set();
|
|
19
16
|
function isStreamEvent(msg) {
|
|
20
17
|
return msg.type === 'stream_event';
|
|
21
18
|
}
|
|
@@ -23,82 +20,33 @@ function isSystemInit(msg) {
|
|
|
23
20
|
const m = msg;
|
|
24
21
|
return m.type === 'system' && m.subtype === 'init';
|
|
25
22
|
}
|
|
26
|
-
function isAssistant(msg) {
|
|
27
|
-
return msg.type === 'assistant';
|
|
28
|
-
}
|
|
29
23
|
function isResult(msg) {
|
|
30
24
|
return msg.type === 'result';
|
|
31
25
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
* @param sessionId 已有的 sessionId,如果为 undefined 则创建新会话
|
|
35
|
-
* @param workDir 工作目录
|
|
36
|
-
* @param model 模型名称
|
|
37
|
-
* @param permissionMode 权限模式
|
|
38
|
-
* @returns SDKSession 对象和实际的 sessionId
|
|
39
|
-
*/
|
|
40
|
-
async function getOrCreateSession(sessionId, _workDir, // 保留参数以备将来使用
|
|
41
|
-
model, permissionMode) {
|
|
42
|
-
const sessionOptions = {
|
|
43
|
-
model: model || 'claude-opus-4-5',
|
|
44
|
-
permissionMode,
|
|
45
|
-
// 可以添加其他选项,如 hooks, allowedTools 等
|
|
46
|
-
};
|
|
47
|
-
let session;
|
|
48
|
-
if (sessionId) {
|
|
49
|
-
// 尝试恢复已有会话
|
|
50
|
-
try {
|
|
51
|
-
log.info(`Attempting to resume session: ${sessionId}`);
|
|
52
|
-
session = unstable_v2_resumeSession(sessionId, sessionOptions);
|
|
53
|
-
activeSessions.set(sessionId, session);
|
|
54
|
-
log.info(`Successfully resumed session: ${sessionId}`);
|
|
55
|
-
return { session, sessionId };
|
|
56
|
-
}
|
|
57
|
-
catch (err) {
|
|
58
|
-
log.warn(`Failed to resume session ${sessionId}, creating new one: ${err}`);
|
|
59
|
-
// 恢复失败,创建新会话
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
// 创建新会话
|
|
63
|
-
session = unstable_v2_createSession(sessionOptions);
|
|
64
|
-
// 新会话的 sessionId 需要从第一个消息中获取
|
|
65
|
-
// 暂时返回 undefined,稍后在 init 消息中获取
|
|
66
|
-
const tempId = `pending-${Date.now()}`;
|
|
67
|
-
activeSessions.set(tempId, session);
|
|
68
|
-
log.info(`Created new session (tempId: ${tempId})`);
|
|
69
|
-
return { session, sessionId: tempId };
|
|
26
|
+
function isAssistant(msg) {
|
|
27
|
+
return msg.type === 'assistant';
|
|
70
28
|
}
|
|
71
29
|
export class ClaudeSDKAdapter {
|
|
72
30
|
toolId = 'claude-sdk';
|
|
73
31
|
/**
|
|
74
|
-
* 清理所有活跃的 SDK
|
|
32
|
+
* 清理所有活跃的 SDK 查询
|
|
75
33
|
*/
|
|
76
34
|
static destroy() {
|
|
77
|
-
for (const
|
|
35
|
+
for (const q of activeQueries) {
|
|
78
36
|
try {
|
|
79
|
-
if (
|
|
80
|
-
|
|
37
|
+
if (q && typeof q.return === 'function') {
|
|
38
|
+
q.return();
|
|
81
39
|
}
|
|
82
40
|
}
|
|
83
41
|
catch {
|
|
84
42
|
/* ignore */
|
|
85
43
|
}
|
|
86
44
|
}
|
|
87
|
-
|
|
88
|
-
for (const session of activeSessions.values()) {
|
|
89
|
-
try {
|
|
90
|
-
session.close();
|
|
91
|
-
}
|
|
92
|
-
catch {
|
|
93
|
-
/* ignore */
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
activeSessions.clear();
|
|
45
|
+
activeQueries.clear();
|
|
97
46
|
}
|
|
98
47
|
run(prompt, sessionId, workDir, callbacks, options) {
|
|
99
48
|
const abortController = new AbortController();
|
|
100
|
-
let
|
|
101
|
-
let actualSessionId;
|
|
49
|
+
let queryClosed = false;
|
|
102
50
|
const permissionMode = options?.skipPermissions
|
|
103
51
|
? 'bypassPermissions'
|
|
104
52
|
: options?.permissionMode === 'acceptEdits'
|
|
@@ -106,47 +54,65 @@ export class ClaudeSDKAdapter {
|
|
|
106
54
|
: options?.permissionMode === 'plan'
|
|
107
55
|
? 'plan'
|
|
108
56
|
: 'default';
|
|
109
|
-
const
|
|
57
|
+
const runQuery = async () => {
|
|
110
58
|
try {
|
|
111
|
-
//
|
|
59
|
+
// 调试:检查关键环境变量
|
|
112
60
|
const hasApiKey = !!process.env.ANTHROPIC_API_KEY;
|
|
113
61
|
const hasAuthToken = !!process.env.ANTHROPIC_AUTH_TOKEN;
|
|
114
|
-
|
|
62
|
+
const hasBaseUrl = !!process.env.ANTHROPIC_BASE_URL;
|
|
63
|
+
if (!hasApiKey && !hasAuthToken && !hasBaseUrl) {
|
|
115
64
|
log.warn('Claude SDK: No API credentials found in environment variables');
|
|
116
65
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
66
|
+
const opts = {
|
|
67
|
+
cwd: workDir,
|
|
68
|
+
resume: sessionId,
|
|
69
|
+
includePartialMessages: true,
|
|
70
|
+
permissionMode,
|
|
71
|
+
model: options?.model,
|
|
72
|
+
abortController,
|
|
73
|
+
allowDangerouslySkipPermissions: permissionMode === 'bypassPermissions',
|
|
74
|
+
};
|
|
75
|
+
log.info(`[ClaudeSDK] Starting query: prompt="${prompt.slice(0, 50)}...", sessionId=${sessionId ?? 'new'}, cwd=${workDir}`);
|
|
76
|
+
let q;
|
|
77
|
+
try {
|
|
78
|
+
q = query({
|
|
79
|
+
prompt,
|
|
80
|
+
options: opts,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch (queryInitErr) {
|
|
84
|
+
log.error(`[ClaudeSDK] Query initialization failed:`, queryInitErr);
|
|
85
|
+
// 如果是会话文件问题,尝试作为新会话
|
|
86
|
+
if (sessionId && (queryInitErr.code === 'ENOENT' ||
|
|
87
|
+
queryInitErr.message.includes('ENOENT') ||
|
|
88
|
+
queryInitErr.message.includes('not found'))) {
|
|
89
|
+
log.warn(`[ClaudeSDK] Session file not found, starting new session`);
|
|
90
|
+
callbacks.onSessionInvalid?.();
|
|
91
|
+
// 重新创建查询,不带 resume
|
|
92
|
+
const newOpts = { ...opts, resume: undefined };
|
|
93
|
+
q = query({
|
|
94
|
+
prompt,
|
|
95
|
+
options: newOpts,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
callbacks.onError(`查询初始化失败: ${queryInitErr.message}`);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// 将查询添加到活跃列表
|
|
104
|
+
activeQueries.add(q);
|
|
125
105
|
let accumulated = '';
|
|
126
106
|
let accumulatedThinking = '';
|
|
127
107
|
const toolStats = {};
|
|
128
108
|
try {
|
|
129
|
-
for await (const msg of
|
|
130
|
-
if (abortController.signal.aborted)
|
|
131
|
-
log.info('Stream aborted by user');
|
|
109
|
+
for await (const msg of q) {
|
|
110
|
+
if (abortController.signal.aborted)
|
|
132
111
|
break;
|
|
133
|
-
}
|
|
134
|
-
// 获取实际的 sessionId(从 init 消息中)
|
|
135
112
|
if (isSystemInit(msg)) {
|
|
136
|
-
|
|
137
|
-
if (newSessionId && newSessionId !== actualSessionId) {
|
|
138
|
-
// 更新 sessionId 映射
|
|
139
|
-
if (actualSessionId && actualSessionId.startsWith('pending-')) {
|
|
140
|
-
activeSessions.delete(actualSessionId);
|
|
141
|
-
}
|
|
142
|
-
activeSessions.set(newSessionId, session);
|
|
143
|
-
actualSessionId = newSessionId;
|
|
144
|
-
log.info(`[V2] Got actual sessionId: ${newSessionId}`);
|
|
145
|
-
callbacks.onSessionId?.(newSessionId);
|
|
146
|
-
}
|
|
113
|
+
callbacks.onSessionId?.(msg.session_id);
|
|
147
114
|
continue;
|
|
148
115
|
}
|
|
149
|
-
// 处理流式事件
|
|
150
116
|
if (isStreamEvent(msg)) {
|
|
151
117
|
const ev = msg.event;
|
|
152
118
|
if (ev?.type === 'content_block_delta' && ev.delta) {
|
|
@@ -161,7 +127,6 @@ export class ClaudeSDKAdapter {
|
|
|
161
127
|
}
|
|
162
128
|
continue;
|
|
163
129
|
}
|
|
164
|
-
// 处理助手消息(工具调用)
|
|
165
130
|
if (isAssistant(msg)) {
|
|
166
131
|
const content = msg.message?.content;
|
|
167
132
|
for (const block of content ?? []) {
|
|
@@ -172,22 +137,16 @@ export class ClaudeSDKAdapter {
|
|
|
172
137
|
}
|
|
173
138
|
continue;
|
|
174
139
|
}
|
|
175
|
-
// 处理结果消息
|
|
176
140
|
if (isResult(msg)) {
|
|
177
|
-
|
|
141
|
+
queryClosed = true;
|
|
178
142
|
const m = msg;
|
|
179
143
|
const success = m.subtype === 'success';
|
|
180
144
|
const errs = m.errors ?? [];
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
log.warn(`Session ${actualSessionId} not found, may need to create new one`);
|
|
187
|
-
callbacks.onSessionInvalid?.();
|
|
188
|
-
}
|
|
189
|
-
const errMsg = errs[0] || '未知错误';
|
|
190
|
-
callbacks.onError(errMsg);
|
|
145
|
+
const noConvErr = errs.find((e) => e.includes('No conversation found with session ID'));
|
|
146
|
+
if (!success && noConvErr) {
|
|
147
|
+
log.warn(`SDK session invalid: ${noConvErr}`);
|
|
148
|
+
callbacks.onSessionInvalid?.();
|
|
149
|
+
callbacks.onError('会话已过期,请发送 /new 开始新会话');
|
|
191
150
|
return;
|
|
192
151
|
}
|
|
193
152
|
const resultText = m.result ?? '';
|
|
@@ -200,55 +159,71 @@ export class ClaudeSDKAdapter {
|
|
|
200
159
|
numTurns: m.num_turns ?? 0,
|
|
201
160
|
toolStats,
|
|
202
161
|
};
|
|
203
|
-
if (!result.accumulated && result.result)
|
|
162
|
+
if (!result.accumulated && result.result)
|
|
204
163
|
result.accumulated = result.result;
|
|
205
|
-
}
|
|
206
164
|
if (!result.accumulated && !result.result && accumulated) {
|
|
165
|
+
log.debug(`Result event had no text but accumulated=${accumulated.length} chars, using accumulated`);
|
|
207
166
|
result.accumulated = accumulated;
|
|
208
167
|
result.result = accumulated;
|
|
209
168
|
}
|
|
169
|
+
if (!result.accumulated && !result.result) {
|
|
170
|
+
const errMsg = errs[0] ?? '未知错误';
|
|
171
|
+
log.warn(`SDK result empty: subtype=${m.subtype}, errors=${JSON.stringify(errs)}`);
|
|
172
|
+
callbacks.onError(errMsg);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
210
175
|
callbacks.onComplete(result);
|
|
211
176
|
return;
|
|
212
177
|
}
|
|
213
178
|
}
|
|
214
|
-
|
|
215
|
-
if (!streamClosed && accumulated) {
|
|
216
|
-
log.info('Stream ended without result message, using accumulated text');
|
|
179
|
+
if (!queryClosed) {
|
|
217
180
|
callbacks.onComplete({
|
|
218
181
|
success: true,
|
|
219
182
|
result: accumulated,
|
|
220
183
|
accumulated,
|
|
221
184
|
cost: 0,
|
|
222
185
|
durationMs: 0,
|
|
223
|
-
numTurns:
|
|
186
|
+
numTurns: 0,
|
|
224
187
|
toolStats,
|
|
225
188
|
});
|
|
226
189
|
}
|
|
227
190
|
}
|
|
228
191
|
finally {
|
|
229
|
-
|
|
230
|
-
|
|
192
|
+
q.close();
|
|
193
|
+
// 从活跃列表中移除
|
|
194
|
+
activeQueries.delete(q);
|
|
231
195
|
}
|
|
232
196
|
}
|
|
233
197
|
catch (err) {
|
|
234
198
|
if (abortController.signal.aborted) {
|
|
235
|
-
log.info('
|
|
199
|
+
log.info('Query aborted by user');
|
|
236
200
|
return;
|
|
237
201
|
}
|
|
238
202
|
const errorObj = err;
|
|
239
203
|
const msg = errorObj.message || String(err);
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
204
|
+
const stack = errorObj.stack || '';
|
|
205
|
+
// 输出详细的错误信息用于调试
|
|
206
|
+
log.error(`Claude SDK error: ${msg}`);
|
|
207
|
+
if (stack) {
|
|
208
|
+
log.error(`Error stack: ${stack}`);
|
|
209
|
+
}
|
|
210
|
+
// 特别处理会话文件不存在的情况
|
|
211
|
+
if (errorObj.code === 'ENOENT' ||
|
|
212
|
+
errorObj.syscall === 'lstat' ||
|
|
213
|
+
msg.includes('ENOENT') ||
|
|
214
|
+
msg.includes('session')) {
|
|
215
|
+
log.warn(`Session file access error, likely session is corrupted or missing`);
|
|
216
|
+
callbacks.onSessionInvalid?.();
|
|
217
|
+
callbacks.onError('会话文件不存在或已损坏,请发送 /new 开始新会话');
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
callbacks.onError(msg);
|
|
243
221
|
}
|
|
244
|
-
callbacks.onError(msg);
|
|
245
222
|
}
|
|
246
223
|
};
|
|
247
|
-
|
|
248
|
-
runSession();
|
|
224
|
+
runQuery();
|
|
249
225
|
return {
|
|
250
226
|
abort: () => {
|
|
251
|
-
log.info('Aborting session run');
|
|
252
227
|
abortController.abort();
|
|
253
228
|
},
|
|
254
229
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getConfiguredAiCommands } from '../config.js';
|
|
2
|
+
import { ClaudeAdapter } from './claude-adapter.js';
|
|
2
3
|
import { ClaudeSDKAdapter } from './claude-sdk-adapter.js';
|
|
3
4
|
import { CodexAdapter } from './codex-adapter.js';
|
|
4
5
|
import { CodeBuddyAdapter } from './codebuddy-adapter.js';
|
|
@@ -9,8 +10,17 @@ export function initAdapters(config) {
|
|
|
9
10
|
adapters.clear();
|
|
10
11
|
for (const aiCommand of getConfiguredAiCommands(config)) {
|
|
11
12
|
if (aiCommand === 'claude') {
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
if (config.useSdkMode) {
|
|
14
|
+
log.info('Claude Agent SDK adapter enabled');
|
|
15
|
+
adapters.set('claude', new ClaudeSDKAdapter());
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
log.info('Claude CLI adapter enabled');
|
|
19
|
+
adapters.set('claude', new ClaudeAdapter(config.claudeCliPath, {
|
|
20
|
+
useProcessPool: true,
|
|
21
|
+
idleTimeoutMs: 2 * 60 * 1000,
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
14
24
|
continue;
|
|
15
25
|
}
|
|
16
26
|
if (aiCommand === 'codex') {
|
|
@@ -28,6 +38,7 @@ export function getAdapter(aiCommand) {
|
|
|
28
38
|
return adapters.get(aiCommand);
|
|
29
39
|
}
|
|
30
40
|
export function cleanupAdapters() {
|
|
41
|
+
ClaudeAdapter.destroy();
|
|
31
42
|
ClaudeSDKAdapter.destroy();
|
|
32
43
|
adapters.clear();
|
|
33
44
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface ClaudeRunCallbacks {
|
|
2
|
+
onText: (accumulated: string) => void;
|
|
3
|
+
onThinking?: (accumulated: string) => void;
|
|
4
|
+
onToolUse?: (toolName: string, toolInput?: Record<string, unknown>) => void;
|
|
5
|
+
onComplete: (result: {
|
|
6
|
+
success: boolean;
|
|
7
|
+
result: string;
|
|
8
|
+
accumulated: string;
|
|
9
|
+
cost: number;
|
|
10
|
+
durationMs: number;
|
|
11
|
+
model?: string;
|
|
12
|
+
numTurns: number;
|
|
13
|
+
toolStats: Record<string, number>;
|
|
14
|
+
}) => void;
|
|
15
|
+
onError: (error: string) => void;
|
|
16
|
+
onSessionId?: (sessionId: string) => void;
|
|
17
|
+
}
|
|
18
|
+
export interface ClaudeRunOptions {
|
|
19
|
+
skipPermissions?: boolean;
|
|
20
|
+
permissionMode?: 'default' | 'acceptEdits' | 'plan';
|
|
21
|
+
timeoutMs?: number;
|
|
22
|
+
model?: string;
|
|
23
|
+
chatId?: string;
|
|
24
|
+
hookPort?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface ClaudeRunHandle {
|
|
27
|
+
abort: () => void;
|
|
28
|
+
}
|
|
29
|
+
export declare function runClaude(cliPath: string, prompt: string, sessionId: string | undefined, workDir: string, callbacks: ClaudeRunCallbacks, options?: ClaudeRunOptions): ClaudeRunHandle;
|