agentstudio 0.1.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/.env +15 -0
- package/README.md +85 -0
- package/dist/bin/agentstudio.d.ts +3 -0
- package/dist/bin/agentstudio.d.ts.map +1 -0
- package/dist/bin/agentstudio.js +141 -0
- package/dist/bin/agentstudio.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +87 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/auth.d.ts +7 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +21 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/routes/agents.d.ts +4 -0
- package/dist/routes/agents.d.ts.map +1 -0
- package/dist/routes/agents.js +804 -0
- package/dist/routes/agents.js.map +1 -0
- package/dist/routes/auth.d.ts +4 -0
- package/dist/routes/auth.d.ts.map +1 -0
- package/dist/routes/auth.js +60 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/files.d.ts +4 -0
- package/dist/routes/files.d.ts.map +1 -0
- package/dist/routes/files.js +301 -0
- package/dist/routes/files.js.map +1 -0
- package/dist/routes/mcp.d.ts +4 -0
- package/dist/routes/mcp.d.ts.map +1 -0
- package/dist/routes/mcp.js +652 -0
- package/dist/routes/mcp.js.map +1 -0
- package/dist/routes/media.d.ts +5 -0
- package/dist/routes/media.d.ts.map +1 -0
- package/dist/routes/media.js +117 -0
- package/dist/routes/media.js.map +1 -0
- package/dist/routes/slides.d.ts +4 -0
- package/dist/routes/slides.d.ts.map +1 -0
- package/dist/routes/slides.js +146 -0
- package/dist/routes/slides.js.map +1 -0
- package/dist/services/claudeSession.d.ts +83 -0
- package/dist/services/claudeSession.d.ts.map +1 -0
- package/dist/services/claudeSession.js +255 -0
- package/dist/services/claudeSession.js.map +1 -0
- package/dist/services/messageQueue.d.ts +31 -0
- package/dist/services/messageQueue.d.ts.map +1 -0
- package/dist/services/messageQueue.js +67 -0
- package/dist/services/messageQueue.js.map +1 -0
- package/dist/services/sessionManager.d.ts +132 -0
- package/dist/services/sessionManager.d.ts.map +1 -0
- package/dist/services/sessionManager.js +439 -0
- package/dist/services/sessionManager.js.map +1 -0
- package/dist/types/claude-history.d.ts +48 -0
- package/dist/types/claude-history.d.ts.map +1 -0
- package/dist/types/claude-history.js +2 -0
- package/dist/types/claude-history.js.map +1 -0
- package/dist/types/claude-versions.d.ts +31 -0
- package/dist/types/claude-versions.d.ts.map +1 -0
- package/dist/types/claude-versions.js +2 -0
- package/dist/types/claude-versions.js.map +1 -0
- package/dist/types/commands.d.ts +32 -0
- package/dist/types/commands.d.ts.map +1 -0
- package/dist/types/commands.js +2 -0
- package/dist/types/commands.js.map +1 -0
- package/dist/types/index.d.ts +81 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +150 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/subagents.d.ts +88 -0
- package/dist/types/subagents.d.ts.map +1 -0
- package/dist/types/subagents.js +2 -0
- package/dist/types/subagents.js.map +1 -0
- package/dist/utils/agentStorage.d.ts +19 -0
- package/dist/utils/agentStorage.d.ts.map +1 -0
- package/dist/utils/agentStorage.js +110 -0
- package/dist/utils/agentStorage.js.map +1 -0
- package/dist/utils/claudeVersionStorage.d.ts +33 -0
- package/dist/utils/claudeVersionStorage.d.ts.map +1 -0
- package/dist/utils/claudeVersionStorage.js +168 -0
- package/dist/utils/claudeVersionStorage.js.map +1 -0
- package/dist/utils/jwt.d.ts +15 -0
- package/dist/utils/jwt.d.ts.map +1 -0
- package/dist/utils/jwt.js +28 -0
- package/dist/utils/jwt.js.map +1 -0
- package/dist/utils/projectMetadataStorage.d.ts +21 -0
- package/dist/utils/projectMetadataStorage.d.ts.map +1 -0
- package/dist/utils/projectMetadataStorage.js +68 -0
- package/dist/utils/projectMetadataStorage.js.map +1 -0
- package/frontend/dist/index.html +86 -0
- package/package.json +66 -0
- package/src/bin/agentstudio.ts +161 -0
- package/src/index.ts +100 -0
- package/src/middleware/auth.ts +26 -0
- package/src/routes/agents.ts +885 -0
- package/src/routes/auth.ts +73 -0
- package/src/routes/commands.ts.bak +441 -0
- package/src/routes/files.ts +352 -0
- package/src/routes/mcp.ts +751 -0
- package/src/routes/media.ts +140 -0
- package/src/routes/projects.ts.bak +601 -0
- package/src/routes/sessions.ts.bak +809 -0
- package/src/routes/settings.ts.bak +718 -0
- package/src/routes/slides.ts +170 -0
- package/src/routes/subagents.ts.bak +364 -0
- package/src/services/claudeSession.ts +293 -0
- package/src/services/messageQueue.ts +71 -0
- package/src/services/sessionManager.ts +532 -0
- package/src/types/claude-history.ts +50 -0
- package/src/types/claude-versions.ts +33 -0
- package/src/types/commands.ts +35 -0
- package/src/types/index.ts +248 -0
- package/src/types/subagents.ts +106 -0
- package/src/utils/agentStorage.ts +126 -0
- package/src/utils/claudeVersionStorage.ts +199 -0
- package/src/utils/jwt.ts +36 -0
- package/src/utils/projectMetadataStorage.ts +86 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,804 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
import * as os from 'os';
|
|
6
|
+
import { exec } from 'child_process';
|
|
7
|
+
import { promisify } from 'util';
|
|
8
|
+
import { AgentStorage } from '../utils/agentStorage.js';
|
|
9
|
+
import { ProjectMetadataStorage } from '../utils/projectMetadataStorage.js';
|
|
10
|
+
import { sessionManager } from '../services/sessionManager.js';
|
|
11
|
+
import { getAllVersions, getDefaultVersionId } from '../utils/claudeVersionStorage.js';
|
|
12
|
+
const router = express.Router();
|
|
13
|
+
const execAsync = promisify(exec);
|
|
14
|
+
// Storage instances
|
|
15
|
+
const globalAgentStorage = new AgentStorage();
|
|
16
|
+
const projectStorage = new ProjectMetadataStorage();
|
|
17
|
+
// Validation schemas
|
|
18
|
+
const CreateAgentSchema = z.object({
|
|
19
|
+
id: z.string().min(1).regex(/^[a-z0-9-_]+$/, 'ID must contain only lowercase letters, numbers, hyphens, and underscores'),
|
|
20
|
+
name: z.string().min(1),
|
|
21
|
+
description: z.string(),
|
|
22
|
+
systemPrompt: z.string().min(1),
|
|
23
|
+
maxTurns: z.number().min(1).max(100).optional().default(25),
|
|
24
|
+
permissionMode: z.enum(['default', 'acceptEdits', 'bypassPermissions', 'plan']).optional().default('acceptEdits'),
|
|
25
|
+
allowedTools: z.array(z.object({
|
|
26
|
+
name: z.string(),
|
|
27
|
+
enabled: z.boolean(),
|
|
28
|
+
permissions: z.object({
|
|
29
|
+
requireConfirmation: z.boolean().optional(),
|
|
30
|
+
allowedPaths: z.array(z.string()).optional(),
|
|
31
|
+
blockedPaths: z.array(z.string()).optional(),
|
|
32
|
+
}).optional()
|
|
33
|
+
})),
|
|
34
|
+
ui: z.object({
|
|
35
|
+
icon: z.string().optional().default('🤖'),
|
|
36
|
+
primaryColor: z.string().optional().default('#3B82F6'),
|
|
37
|
+
headerTitle: z.string(),
|
|
38
|
+
headerDescription: z.string(),
|
|
39
|
+
welcomeMessage: z.string().optional(),
|
|
40
|
+
componentType: z.enum(['slides', 'chat', 'documents', 'code', 'custom']),
|
|
41
|
+
customComponent: z.string().optional()
|
|
42
|
+
}),
|
|
43
|
+
workingDirectory: z.string().optional(),
|
|
44
|
+
dataDirectory: z.string().optional(),
|
|
45
|
+
fileTypes: z.array(z.string()).optional(),
|
|
46
|
+
author: z.string().min(1),
|
|
47
|
+
homepage: z.string().url().optional(),
|
|
48
|
+
tags: z.array(z.string()).optional().default([]),
|
|
49
|
+
enabled: z.boolean().optional().default(true)
|
|
50
|
+
});
|
|
51
|
+
const UpdateAgentSchema = CreateAgentSchema.partial().omit({ id: true });
|
|
52
|
+
// 获取活跃会话列表 (需要在通用获取agents路由之前)
|
|
53
|
+
router.get('/sessions', (req, res) => {
|
|
54
|
+
try {
|
|
55
|
+
const activeCount = sessionManager.getActiveSessionCount();
|
|
56
|
+
const sessionsInfo = sessionManager.getSessionsInfo();
|
|
57
|
+
res.json({
|
|
58
|
+
activeSessionCount: activeCount,
|
|
59
|
+
sessions: sessionsInfo,
|
|
60
|
+
message: `${activeCount} active Claude sessions`
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
console.error('Failed to get sessions:', error);
|
|
65
|
+
res.status(500).json({ error: 'Failed to retrieve session info' });
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
// 手动关闭指定会话
|
|
69
|
+
router.delete('/sessions/:sessionId', async (req, res) => {
|
|
70
|
+
try {
|
|
71
|
+
const { sessionId } = req.params;
|
|
72
|
+
const removed = await sessionManager.removeSession(sessionId);
|
|
73
|
+
if (removed) {
|
|
74
|
+
res.json({ success: true, message: `Session ${sessionId} closed` });
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
res.status(404).json({ error: 'Session not found' });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
console.error('Failed to close session:', error);
|
|
82
|
+
res.status(500).json({ error: 'Failed to close session' });
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
// 中断指定会话的当前请求
|
|
86
|
+
router.post('/sessions/:sessionId/interrupt', async (req, res) => {
|
|
87
|
+
try {
|
|
88
|
+
const { sessionId } = req.params;
|
|
89
|
+
console.log(`🛑 API: Interrupt request for session: ${sessionId}`);
|
|
90
|
+
const result = await sessionManager.interruptSession(sessionId);
|
|
91
|
+
if (result.success) {
|
|
92
|
+
res.json({
|
|
93
|
+
success: true,
|
|
94
|
+
message: `Session ${sessionId} interrupted successfully`
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
res.status(result.error === 'Session not found' ? 404 : 500).json({
|
|
99
|
+
success: false,
|
|
100
|
+
error: result.error || 'Failed to interrupt session'
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
console.error('Failed to interrupt session:', error);
|
|
106
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
107
|
+
res.status(500).json({
|
|
108
|
+
success: false,
|
|
109
|
+
error: 'Failed to interrupt session',
|
|
110
|
+
details: errorMessage
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
// Get all agents
|
|
115
|
+
router.get('/', async (req, res) => {
|
|
116
|
+
try {
|
|
117
|
+
const { enabled, type } = req.query;
|
|
118
|
+
let agents = await globalAgentStorage.getAllAgents();
|
|
119
|
+
// Filter by enabled status
|
|
120
|
+
if (enabled !== undefined) {
|
|
121
|
+
const isEnabled = enabled === 'true';
|
|
122
|
+
agents = agents.filter(agent => agent.enabled === isEnabled);
|
|
123
|
+
}
|
|
124
|
+
// Filter by component type
|
|
125
|
+
if (type && typeof type === 'string') {
|
|
126
|
+
agents = agents.filter(agent => agent.ui.componentType === type);
|
|
127
|
+
}
|
|
128
|
+
res.json({ agents });
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
console.error('Failed to get agents:', error);
|
|
132
|
+
res.status(500).json({ error: 'Failed to retrieve agents' });
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
// Get specific agent
|
|
136
|
+
router.get('/:agentId', async (req, res) => {
|
|
137
|
+
try {
|
|
138
|
+
const { agentId } = req.params;
|
|
139
|
+
const agent = await globalAgentStorage.getAgent(agentId);
|
|
140
|
+
if (!agent) {
|
|
141
|
+
return res.status(404).json({ error: 'Agent not found' });
|
|
142
|
+
}
|
|
143
|
+
res.json({ agent });
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
console.error('Failed to get agent:', error);
|
|
147
|
+
res.status(500).json({ error: 'Failed to retrieve agent' });
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
// Create new agent
|
|
151
|
+
router.post('/', async (req, res) => {
|
|
152
|
+
try {
|
|
153
|
+
const validation = CreateAgentSchema.safeParse(req.body);
|
|
154
|
+
if (!validation.success) {
|
|
155
|
+
return res.status(400).json({ error: 'Invalid agent data', details: validation.error });
|
|
156
|
+
}
|
|
157
|
+
const agentData = validation.data;
|
|
158
|
+
// Check if agent ID already exists
|
|
159
|
+
const existingAgent = await globalAgentStorage.getAgent(agentData.id);
|
|
160
|
+
if (existingAgent) {
|
|
161
|
+
return res.status(409).json({ error: 'Agent with this ID already exists' });
|
|
162
|
+
}
|
|
163
|
+
const agent = await globalAgentStorage.createAgent({
|
|
164
|
+
version: '1.0.0',
|
|
165
|
+
model: 'claude-3-5-sonnet-20241022',
|
|
166
|
+
...agentData
|
|
167
|
+
});
|
|
168
|
+
res.json({ agent, message: 'Agent created successfully' });
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
console.error('Failed to create agent:', error);
|
|
172
|
+
res.status(500).json({ error: 'Failed to create agent' });
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
// Update agent
|
|
176
|
+
router.put('/:agentId', async (req, res) => {
|
|
177
|
+
try {
|
|
178
|
+
const { agentId } = req.params;
|
|
179
|
+
const validation = UpdateAgentSchema.safeParse(req.body);
|
|
180
|
+
if (!validation.success) {
|
|
181
|
+
return res.status(400).json({ error: 'Invalid agent data', details: validation.error });
|
|
182
|
+
}
|
|
183
|
+
const existingAgent = await globalAgentStorage.getAgent(agentId);
|
|
184
|
+
if (!existingAgent) {
|
|
185
|
+
return res.status(404).json({ error: 'Agent not found' });
|
|
186
|
+
}
|
|
187
|
+
const updatedAgent = {
|
|
188
|
+
...existingAgent,
|
|
189
|
+
...validation.data,
|
|
190
|
+
id: agentId, // Ensure ID doesn't change
|
|
191
|
+
updatedAt: new Date().toISOString()
|
|
192
|
+
};
|
|
193
|
+
await globalAgentStorage.saveAgent(updatedAgent);
|
|
194
|
+
res.json({ agent: updatedAgent, message: 'Agent updated successfully' });
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
console.error('Failed to update agent:', error);
|
|
198
|
+
res.status(500).json({ error: 'Failed to update agent' });
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
// Delete agent
|
|
202
|
+
router.delete('/:agentId', async (req, res) => {
|
|
203
|
+
try {
|
|
204
|
+
const { agentId } = req.params;
|
|
205
|
+
const existingAgent = await globalAgentStorage.getAgent(agentId);
|
|
206
|
+
if (!existingAgent) {
|
|
207
|
+
return res.status(404).json({ error: 'Agent not found' });
|
|
208
|
+
}
|
|
209
|
+
await globalAgentStorage.deleteAgent(agentId);
|
|
210
|
+
res.json({ success: true, message: 'Agent deleted successfully' });
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
console.error('Failed to delete agent:', error);
|
|
214
|
+
res.status(500).json({ error: 'Failed to delete agent' });
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
// Validation schemas for chat
|
|
218
|
+
const ImageSchema = z.object({
|
|
219
|
+
id: z.string(),
|
|
220
|
+
data: z.string(), // base64 encoded image data
|
|
221
|
+
mediaType: z.enum(['image/jpeg', 'image/png', 'image/gif', 'image/webp']),
|
|
222
|
+
filename: z.string().optional()
|
|
223
|
+
});
|
|
224
|
+
const ChatRequestSchema = z.object({
|
|
225
|
+
message: z.string(),
|
|
226
|
+
images: z.array(ImageSchema).optional(),
|
|
227
|
+
agentId: z.string().min(1),
|
|
228
|
+
sessionId: z.string().optional().nullable(),
|
|
229
|
+
projectPath: z.string().optional(),
|
|
230
|
+
mcpTools: z.array(z.string()).optional(),
|
|
231
|
+
permissionMode: z.enum(['default', 'acceptEdits', 'bypassPermissions', 'plan']).optional(),
|
|
232
|
+
model: z.string().optional(),
|
|
233
|
+
claudeVersion: z.string().optional(), // Claude版本ID
|
|
234
|
+
context: z.object({
|
|
235
|
+
currentSlide: z.number().optional().nullable(),
|
|
236
|
+
slideContent: z.string().optional(),
|
|
237
|
+
allSlides: z.array(z.object({
|
|
238
|
+
index: z.number(),
|
|
239
|
+
title: z.string(),
|
|
240
|
+
path: z.string(),
|
|
241
|
+
exists: z.boolean().optional()
|
|
242
|
+
})).optional(),
|
|
243
|
+
// Generic context for other agent types
|
|
244
|
+
currentItem: z.any().optional(),
|
|
245
|
+
allItems: z.array(z.any()).optional(),
|
|
246
|
+
customContext: z.record(z.any()).optional()
|
|
247
|
+
}).optional()
|
|
248
|
+
}).refine(data => data.message.trim().length > 0 || (data.images && data.images.length > 0), {
|
|
249
|
+
message: "Either message text or images must be provided"
|
|
250
|
+
});
|
|
251
|
+
// Function to get the path to system claude command
|
|
252
|
+
async function getClaudeExecutablePath() {
|
|
253
|
+
try {
|
|
254
|
+
const { stdout: claudePath } = await execAsync('which claude');
|
|
255
|
+
if (!claudePath)
|
|
256
|
+
return null;
|
|
257
|
+
const cleanPath = claudePath.trim();
|
|
258
|
+
// Skip local node_modules paths - we want global installation
|
|
259
|
+
if (cleanPath.includes('node_modules/.bin')) {
|
|
260
|
+
// Try to find global installation by checking PATH without local node_modules
|
|
261
|
+
try {
|
|
262
|
+
const { stdout: allClaudes } = await execAsync('which -a claude');
|
|
263
|
+
const claudes = allClaudes.trim().split('\n');
|
|
264
|
+
// Find the first non-local installation
|
|
265
|
+
for (const claudePathOption of claudes) {
|
|
266
|
+
if (!claudePathOption.includes('node_modules/.bin')) {
|
|
267
|
+
return claudePathOption.trim();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
// Fallback to the first path found
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return cleanPath;
|
|
276
|
+
}
|
|
277
|
+
catch (error) {
|
|
278
|
+
console.error('Failed to get claude executable path:', error);
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// Helper functions for chat endpoint
|
|
283
|
+
/**
|
|
284
|
+
* 设置 SSE 连接管理
|
|
285
|
+
*/
|
|
286
|
+
function setupSSEConnectionManagement(req, res, agentId) {
|
|
287
|
+
// 连接管理变量
|
|
288
|
+
let isConnectionClosed = false;
|
|
289
|
+
let connectionTimeout = null;
|
|
290
|
+
let currentRequestId = null;
|
|
291
|
+
let claudeSession; // 会话实例,稍后赋值
|
|
292
|
+
// 安全关闭连接的函数
|
|
293
|
+
const safeCloseConnection = (reason) => {
|
|
294
|
+
if (isConnectionClosed)
|
|
295
|
+
return;
|
|
296
|
+
isConnectionClosed = true;
|
|
297
|
+
console.log(`🔚 Closing SSE connection for agent ${agentId}: ${reason}`);
|
|
298
|
+
// 清理超时定时器
|
|
299
|
+
if (connectionTimeout) {
|
|
300
|
+
clearTimeout(connectionTimeout);
|
|
301
|
+
connectionTimeout = null;
|
|
302
|
+
}
|
|
303
|
+
// 清理 Claude 请求回调
|
|
304
|
+
if (currentRequestId && claudeSession) {
|
|
305
|
+
claudeSession.cancelRequest(currentRequestId);
|
|
306
|
+
if (reason === 'request completed') {
|
|
307
|
+
console.log(`✅ Cleaned up Claude request ${currentRequestId}: ${reason}`);
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
console.log(`🚫 Cancelled Claude request ${currentRequestId} due to: ${reason}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
// 确保连接关闭
|
|
314
|
+
if (!res.headersSent) {
|
|
315
|
+
try {
|
|
316
|
+
res.write(`data: ${JSON.stringify({
|
|
317
|
+
type: 'connection_closed',
|
|
318
|
+
reason: reason,
|
|
319
|
+
timestamp: Date.now()
|
|
320
|
+
})}\n\n`);
|
|
321
|
+
}
|
|
322
|
+
catch (writeError) {
|
|
323
|
+
console.error('Failed to write connection close event:', writeError);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
try {
|
|
327
|
+
if (!res.destroyed) {
|
|
328
|
+
res.end();
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
catch (endError) {
|
|
332
|
+
console.error('Failed to end response:', endError);
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
// 监听客户端断开连接 - 只在响应阶段监听
|
|
336
|
+
res.on('close', () => {
|
|
337
|
+
if (!isConnectionClosed) {
|
|
338
|
+
safeCloseConnection('client disconnected');
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
// 监听请求完成
|
|
342
|
+
req.on('end', () => {
|
|
343
|
+
console.log('📤 Request data received completely');
|
|
344
|
+
});
|
|
345
|
+
// 监听连接错误
|
|
346
|
+
req.on('error', (error) => {
|
|
347
|
+
console.error('SSE request error:', error);
|
|
348
|
+
safeCloseConnection(`request error: ${error.message}`);
|
|
349
|
+
});
|
|
350
|
+
// 监听响应错误
|
|
351
|
+
res.on('error', (error) => {
|
|
352
|
+
console.error('SSE response error:', error);
|
|
353
|
+
safeCloseConnection(`response error: ${error.message}`);
|
|
354
|
+
});
|
|
355
|
+
// 设置连接超时保护(30分钟)
|
|
356
|
+
const CONNECTION_TIMEOUT_MS = 30 * 60 * 1000;
|
|
357
|
+
connectionTimeout = setTimeout(() => {
|
|
358
|
+
safeCloseConnection('connection timeout');
|
|
359
|
+
}, CONNECTION_TIMEOUT_MS);
|
|
360
|
+
return {
|
|
361
|
+
isConnectionClosed: () => isConnectionClosed,
|
|
362
|
+
safeCloseConnection,
|
|
363
|
+
setCurrentRequestId: (id) => { currentRequestId = id; },
|
|
364
|
+
setClaudeSession: (session) => { claudeSession = session; }
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* 构建查询选项
|
|
369
|
+
*/
|
|
370
|
+
async function buildQueryOptions(agent, projectPath, mcpTools, permissionMode, model, claudeVersion) {
|
|
371
|
+
// Use Claude Code SDK with agent-specific settings
|
|
372
|
+
// If projectPath is provided, use it as cwd; otherwise fall back to agent's workingDirectory
|
|
373
|
+
let cwd = process.cwd();
|
|
374
|
+
if (projectPath) {
|
|
375
|
+
cwd = projectPath;
|
|
376
|
+
}
|
|
377
|
+
else if (agent.workingDirectory) {
|
|
378
|
+
cwd = path.resolve(process.cwd(), agent.workingDirectory);
|
|
379
|
+
}
|
|
380
|
+
// Determine permission mode: request > agent config > system default
|
|
381
|
+
let finalPermissionMode = 'default';
|
|
382
|
+
if (permissionMode) {
|
|
383
|
+
finalPermissionMode = permissionMode;
|
|
384
|
+
}
|
|
385
|
+
else if (agent.permissionMode) {
|
|
386
|
+
finalPermissionMode = agent.permissionMode;
|
|
387
|
+
}
|
|
388
|
+
// Determine model: request > agent config > system default (sonnet)
|
|
389
|
+
let finalModel = 'sonnet';
|
|
390
|
+
if (model) {
|
|
391
|
+
finalModel = model;
|
|
392
|
+
}
|
|
393
|
+
else if (agent.model) {
|
|
394
|
+
finalModel = agent.model;
|
|
395
|
+
}
|
|
396
|
+
// Build allowed tools list from agent configuration
|
|
397
|
+
const allowedTools = agent.allowedTools
|
|
398
|
+
.filter((tool) => tool.enabled)
|
|
399
|
+
.map((tool) => tool.name);
|
|
400
|
+
// Add MCP tools if provided
|
|
401
|
+
if (mcpTools && mcpTools.length > 0) {
|
|
402
|
+
allowedTools.push(...mcpTools);
|
|
403
|
+
}
|
|
404
|
+
// 获取Claude可执行路径 - 支持版本选择
|
|
405
|
+
let executablePath = null;
|
|
406
|
+
let environmentVariables = {};
|
|
407
|
+
try {
|
|
408
|
+
if (claudeVersion) {
|
|
409
|
+
// 使用指定版本
|
|
410
|
+
const versions = await getAllVersions();
|
|
411
|
+
const selectedVersion = versions.find(v => v.id === claudeVersion);
|
|
412
|
+
if (selectedVersion) {
|
|
413
|
+
if (selectedVersion.executablePath) {
|
|
414
|
+
executablePath = selectedVersion.executablePath.trim();
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
executablePath = await getClaudeExecutablePath();
|
|
418
|
+
}
|
|
419
|
+
environmentVariables = selectedVersion.environmentVariables || {};
|
|
420
|
+
console.log(`🎯 Using specified Claude version: ${selectedVersion.alias} (${executablePath})`);
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
console.warn(`⚠️ Specified Claude version not found: ${claudeVersion}, falling back to default`);
|
|
424
|
+
executablePath = await getClaudeExecutablePath();
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
// 使用默认版本
|
|
429
|
+
const defaultVersionId = await getDefaultVersionId();
|
|
430
|
+
if (defaultVersionId) {
|
|
431
|
+
const versions = await getAllVersions();
|
|
432
|
+
const defaultVersion = versions.find(v => v.id === defaultVersionId);
|
|
433
|
+
if (defaultVersion) {
|
|
434
|
+
if (defaultVersion.executablePath) {
|
|
435
|
+
executablePath = defaultVersion.executablePath;
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
executablePath = await getClaudeExecutablePath();
|
|
439
|
+
}
|
|
440
|
+
environmentVariables = defaultVersion.environmentVariables || {};
|
|
441
|
+
console.log(`🎯 Using default Claude version: ${defaultVersion.alias} (${executablePath})`);
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
executablePath = await getClaudeExecutablePath();
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
executablePath = await getClaudeExecutablePath();
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
catch (error) {
|
|
453
|
+
console.error('Failed to get Claude executable path:', error);
|
|
454
|
+
executablePath = await getClaudeExecutablePath();
|
|
455
|
+
}
|
|
456
|
+
console.log(`🎯 Using Claude executable path: ${executablePath}`);
|
|
457
|
+
const queryOptions = {
|
|
458
|
+
appendSystemPrompt: agent.systemPrompt,
|
|
459
|
+
allowedTools,
|
|
460
|
+
maxTurns: agent.maxTurns,
|
|
461
|
+
cwd,
|
|
462
|
+
permissionMode: finalPermissionMode,
|
|
463
|
+
model: finalModel,
|
|
464
|
+
};
|
|
465
|
+
// Only add pathToClaudeCodeExecutable if we have a valid path
|
|
466
|
+
if (executablePath) {
|
|
467
|
+
queryOptions.pathToClaudeCodeExecutable = executablePath;
|
|
468
|
+
}
|
|
469
|
+
// Add environment variables if any
|
|
470
|
+
if (Object.keys(environmentVariables).length > 0) {
|
|
471
|
+
// 合并用户环境变量和当前进程环境变量,避免丢失关键的系统环境变量如PATH
|
|
472
|
+
queryOptions.env = { ...process.env, ...environmentVariables };
|
|
473
|
+
console.log(`🌍 Using environment variables:`, environmentVariables);
|
|
474
|
+
}
|
|
475
|
+
// Add MCP configuration if MCP tools are selected
|
|
476
|
+
if (mcpTools && mcpTools.length > 0) {
|
|
477
|
+
try {
|
|
478
|
+
const mcpConfigContent = readMcpConfig();
|
|
479
|
+
// Extract unique server names from mcpTools
|
|
480
|
+
const serverNames = new Set();
|
|
481
|
+
for (const tool of mcpTools) {
|
|
482
|
+
// Tool format: mcp__serverName__toolName or mcp__serverName
|
|
483
|
+
const parts = tool.split('__');
|
|
484
|
+
if (parts.length >= 2 && parts[0] === 'mcp') {
|
|
485
|
+
serverNames.add(parts[1]);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
// Build mcpServers configuration
|
|
489
|
+
const mcpServers = {};
|
|
490
|
+
for (const serverName of serverNames) {
|
|
491
|
+
const serverConfig = mcpConfigContent.mcpServers?.[serverName];
|
|
492
|
+
if (serverConfig && serverConfig.status === 'active') {
|
|
493
|
+
if (serverConfig.type === 'http') {
|
|
494
|
+
mcpServers[serverName] = {
|
|
495
|
+
type: 'http',
|
|
496
|
+
url: serverConfig.url
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
else if (serverConfig.type === 'stdio') {
|
|
500
|
+
mcpServers[serverName] = {
|
|
501
|
+
type: 'stdio',
|
|
502
|
+
command: serverConfig.command,
|
|
503
|
+
args: serverConfig.args || [],
|
|
504
|
+
env: serverConfig.env || {}
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if (Object.keys(mcpServers).length > 0) {
|
|
510
|
+
queryOptions.mcpServers = mcpServers;
|
|
511
|
+
console.log('🔧 MCP Servers configured:', Object.keys(mcpServers));
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
catch (error) {
|
|
515
|
+
console.error('Failed to parse MCP configuration:', error);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return queryOptions;
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* 处理会话管理逻辑
|
|
522
|
+
*/
|
|
523
|
+
async function handleSessionManagement(agentId, sessionId, projectPath, queryOptions, claudeVersionId) {
|
|
524
|
+
let claudeSession;
|
|
525
|
+
let actualSessionId = sessionId || null;
|
|
526
|
+
if (sessionId) {
|
|
527
|
+
// 尝试复用现有会话
|
|
528
|
+
console.log(`🔍 Looking for existing session: ${sessionId} for agent: ${agentId}`);
|
|
529
|
+
claudeSession = sessionManager.getSession(sessionId);
|
|
530
|
+
if (claudeSession) {
|
|
531
|
+
console.log(`♻️ Using existing persistent Claude session: ${sessionId} for agent: ${agentId}`);
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
console.log(`❌ Session ${sessionId} not found in memory for agent: ${agentId}`);
|
|
535
|
+
// 检查项目目录中是否存在会话历史
|
|
536
|
+
console.log(`🔍 Checking project directory for session history: ${sessionId}, projectPath: ${projectPath}`);
|
|
537
|
+
const sessionExists = sessionManager.checkSessionExists(sessionId, projectPath);
|
|
538
|
+
console.log(`📁 Session history exists: ${sessionExists} for sessionId: ${sessionId}`);
|
|
539
|
+
if (sessionExists) {
|
|
540
|
+
// 会话历史存在,使用 resume 参数恢复会话
|
|
541
|
+
console.log(`🔄 Found session history for ${sessionId}, resuming session for agent: ${agentId}`);
|
|
542
|
+
claudeSession = sessionManager.createNewSession(agentId, queryOptions, sessionId, claudeVersionId);
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
// 会话历史不存在,创建新会话但保持原始 sessionId 用于前端识别
|
|
546
|
+
console.log(`⚠️ Session ${sessionId} not found in memory or project history, creating new session for agent: ${agentId}`);
|
|
547
|
+
claudeSession = sessionManager.createNewSession(agentId, queryOptions, undefined, claudeVersionId);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
// 创建新的持续会话
|
|
553
|
+
claudeSession = sessionManager.createNewSession(agentId, queryOptions, undefined, claudeVersionId);
|
|
554
|
+
console.log(`🆕 Created new persistent Claude session for agent: ${agentId}`);
|
|
555
|
+
}
|
|
556
|
+
return { claudeSession, actualSessionId };
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* 构建用户消息内容
|
|
560
|
+
*/
|
|
561
|
+
function buildUserMessageContent(message, images) {
|
|
562
|
+
const messageContent = [];
|
|
563
|
+
// Add text content if provided
|
|
564
|
+
if (message && message.trim()) {
|
|
565
|
+
messageContent.push({
|
|
566
|
+
type: "text",
|
|
567
|
+
text: message
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
// Add image content
|
|
571
|
+
if (images && images.length > 0) {
|
|
572
|
+
console.log('📸 Processing images:', images.map(img => ({
|
|
573
|
+
id: img.id,
|
|
574
|
+
mediaType: img.mediaType,
|
|
575
|
+
filename: img.filename,
|
|
576
|
+
size: img.data.length
|
|
577
|
+
})));
|
|
578
|
+
for (const image of images) {
|
|
579
|
+
messageContent.push({
|
|
580
|
+
type: "image",
|
|
581
|
+
source: {
|
|
582
|
+
type: "base64",
|
|
583
|
+
media_type: image.mediaType,
|
|
584
|
+
data: image.data
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return {
|
|
590
|
+
type: "user",
|
|
591
|
+
message: {
|
|
592
|
+
role: "user",
|
|
593
|
+
content: messageContent
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
// POST /api/agents/chat - Agent-based AI chat using Claude Code SDK with session management
|
|
598
|
+
router.post('/chat', async (req, res) => {
|
|
599
|
+
try {
|
|
600
|
+
console.log('Chat request received:', req.body);
|
|
601
|
+
// 输出当前Session Manager的状态
|
|
602
|
+
console.log('📊 SessionManager状态 - 收到/chat消息时:');
|
|
603
|
+
console.log(` 活跃会话总数: ${sessionManager.getActiveSessionCount()}`);
|
|
604
|
+
const sessionsInfo = sessionManager.getSessionsInfo();
|
|
605
|
+
console.log(' 会话详情:');
|
|
606
|
+
sessionsInfo.forEach(session => {
|
|
607
|
+
console.log(` - SessionId: ${session.sessionId}`);
|
|
608
|
+
console.log(` AgentId: ${session.agentId}`);
|
|
609
|
+
console.log(` 状态: ${session.status}`);
|
|
610
|
+
console.log(` 是否活跃: ${session.isActive}`);
|
|
611
|
+
console.log(` 空闲时间: ${Math.round(session.idleTimeMs / 1000)}秒`);
|
|
612
|
+
console.log(` 最后活动: ${new Date(session.lastActivity).toISOString()}`);
|
|
613
|
+
});
|
|
614
|
+
// 验证请求数据
|
|
615
|
+
const validation = ChatRequestSchema.safeParse(req.body);
|
|
616
|
+
if (!validation.success) {
|
|
617
|
+
console.log('Validation failed:', validation.error);
|
|
618
|
+
return res.status(400).json({ error: 'Invalid request body', details: validation.error });
|
|
619
|
+
}
|
|
620
|
+
const { message, images, agentId, sessionId, projectPath, mcpTools, permissionMode, model, claudeVersion } = validation.data;
|
|
621
|
+
// 获取 agent 配置
|
|
622
|
+
const agent = await globalAgentStorage.getAgent(agentId);
|
|
623
|
+
if (!agent) {
|
|
624
|
+
return res.status(404).json({ error: 'Agent not found' });
|
|
625
|
+
}
|
|
626
|
+
if (!agent.enabled) {
|
|
627
|
+
return res.status(403).json({ error: 'Agent is disabled' });
|
|
628
|
+
}
|
|
629
|
+
// 设置 SSE 响应头
|
|
630
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
631
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
632
|
+
res.setHeader('Connection', 'keep-alive');
|
|
633
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
634
|
+
res.setHeader('Access-Control-Allow-Headers', 'Cache-Control');
|
|
635
|
+
// 设置连接管理
|
|
636
|
+
const connectionManager = setupSSEConnectionManagement(req, res, agentId);
|
|
637
|
+
try {
|
|
638
|
+
// 构建查询选项
|
|
639
|
+
const queryOptions = await buildQueryOptions(agent, projectPath, mcpTools, permissionMode, model, claudeVersion);
|
|
640
|
+
// 处理会话管理
|
|
641
|
+
const { claudeSession, actualSessionId: initialSessionId } = await handleSessionManagement(agentId, sessionId || null, projectPath, queryOptions, claudeVersion);
|
|
642
|
+
let actualSessionId = initialSessionId;
|
|
643
|
+
// 设置会话到连接管理器
|
|
644
|
+
connectionManager.setClaudeSession(claudeSession);
|
|
645
|
+
// 构建用户消息
|
|
646
|
+
const userMessage = buildUserMessageContent(message, images);
|
|
647
|
+
// 为这个特定请求创建一个独立的query调用,但复用session context
|
|
648
|
+
const currentSessionId = claudeSession.getClaudeSessionId();
|
|
649
|
+
// 构建完整的query options,如果有现有session则使用resume
|
|
650
|
+
const requestQueryOptions = { ...queryOptions };
|
|
651
|
+
if (currentSessionId) {
|
|
652
|
+
requestQueryOptions.resume = currentSessionId;
|
|
653
|
+
console.log(`🔄 Using resume sessionId: ${currentSessionId} for this request`);
|
|
654
|
+
}
|
|
655
|
+
// 使用会话的 sendMessage 方法发送消息
|
|
656
|
+
const currentRequestId = await claudeSession.sendMessage(userMessage, (sdkMessage) => {
|
|
657
|
+
// 检查连接是否已关闭
|
|
658
|
+
if (connectionManager.isConnectionClosed()) {
|
|
659
|
+
console.log(`⚠️ Skipping response for closed connection, agent: ${agentId}`);
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
// 当收到 init 消息时,确认会话 ID
|
|
663
|
+
const responseSessionId = sdkMessage.session_id || sdkMessage.sessionId;
|
|
664
|
+
if (sdkMessage.type === 'system' && sdkMessage.subtype === 'init' && responseSessionId) {
|
|
665
|
+
if (!actualSessionId || !currentSessionId) {
|
|
666
|
+
// 新会话:保存session ID
|
|
667
|
+
claudeSession.setClaudeSessionId(responseSessionId);
|
|
668
|
+
sessionManager.confirmSessionId(claudeSession, responseSessionId);
|
|
669
|
+
console.log(`✅ Confirmed session ${responseSessionId} for agent: ${agentId}`);
|
|
670
|
+
}
|
|
671
|
+
else if (currentSessionId && responseSessionId !== currentSessionId) {
|
|
672
|
+
// Resume场景:Claude SDK返回了新的session ID,需要通知前端
|
|
673
|
+
console.log(`🔄 Session resumed: ${currentSessionId} -> ${responseSessionId} for agent: ${agentId}`);
|
|
674
|
+
// 更新会话管理器中的session ID映射
|
|
675
|
+
sessionManager.replaceSessionId(claudeSession, currentSessionId, responseSessionId);
|
|
676
|
+
claudeSession.setClaudeSessionId(responseSessionId);
|
|
677
|
+
// 发送session resume通知给前端
|
|
678
|
+
const resumeNotification = {
|
|
679
|
+
type: 'session_resumed',
|
|
680
|
+
subtype: 'new_branch',
|
|
681
|
+
originalSessionId: currentSessionId,
|
|
682
|
+
newSessionId: responseSessionId,
|
|
683
|
+
sessionId: responseSessionId,
|
|
684
|
+
message: `会话已从历史记录恢复并创建新分支。原始会话ID: ${currentSessionId},新会话ID: ${responseSessionId}`,
|
|
685
|
+
timestamp: Date.now()
|
|
686
|
+
};
|
|
687
|
+
try {
|
|
688
|
+
if (!res.destroyed && !connectionManager.isConnectionClosed()) {
|
|
689
|
+
res.write(`data: ${JSON.stringify(resumeNotification)}\n\n`);
|
|
690
|
+
console.log(`🔄 Sent session resume notification: ${currentSessionId} -> ${responseSessionId}`);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
catch (writeError) {
|
|
694
|
+
console.error('Failed to write session resume notification:', writeError);
|
|
695
|
+
}
|
|
696
|
+
// 更新实际的session ID为新的ID
|
|
697
|
+
actualSessionId = responseSessionId;
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
// 继续会话:使用现有session ID
|
|
701
|
+
console.log(`♻️ Continued session ${currentSessionId} for agent: ${agentId}`);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
const eventData = {
|
|
705
|
+
...sdkMessage,
|
|
706
|
+
agentId: agentId,
|
|
707
|
+
sessionId: actualSessionId || responseSessionId || currentSessionId,
|
|
708
|
+
timestamp: Date.now()
|
|
709
|
+
};
|
|
710
|
+
// 确保返回的 session_id 字段与 sessionId 一致
|
|
711
|
+
if (actualSessionId || currentSessionId) {
|
|
712
|
+
eventData.session_id = actualSessionId || currentSessionId;
|
|
713
|
+
}
|
|
714
|
+
try {
|
|
715
|
+
if (!res.destroyed && !connectionManager.isConnectionClosed()) {
|
|
716
|
+
res.write(`data: ${JSON.stringify(eventData)}\n\n`);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
catch (writeError) {
|
|
720
|
+
console.error('Failed to write SSE data:', writeError);
|
|
721
|
+
const errorMessage = writeError instanceof Error ? writeError.message : 'unknown write error';
|
|
722
|
+
connectionManager.safeCloseConnection(`write error: ${errorMessage}`);
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
// 当收到 result 事件时,正常结束 SSE 连接
|
|
726
|
+
if (sdkMessage.type === 'result') {
|
|
727
|
+
console.log(`✅ Received result event, closing SSE connection for sessionId: ${actualSessionId || currentSessionId}`);
|
|
728
|
+
connectionManager.safeCloseConnection('request completed');
|
|
729
|
+
}
|
|
730
|
+
});
|
|
731
|
+
// 设置当前请求ID到连接管理器
|
|
732
|
+
connectionManager.setCurrentRequestId(currentRequestId);
|
|
733
|
+
console.log(`📨 Started Claude request for agent: ${agentId}, sessionId: ${currentSessionId || 'new'}, requestId: ${currentRequestId}`);
|
|
734
|
+
}
|
|
735
|
+
catch (sessionError) {
|
|
736
|
+
console.error('Claude session error:', sessionError);
|
|
737
|
+
const errorMessage = sessionError instanceof Error ? sessionError.message : 'Unknown error';
|
|
738
|
+
if (!connectionManager.isConnectionClosed()) {
|
|
739
|
+
try {
|
|
740
|
+
res.write(`data: ${JSON.stringify({
|
|
741
|
+
type: 'error',
|
|
742
|
+
error: 'Claude session failed',
|
|
743
|
+
message: errorMessage,
|
|
744
|
+
timestamp: Date.now()
|
|
745
|
+
})}\n\n`);
|
|
746
|
+
}
|
|
747
|
+
catch (writeError) {
|
|
748
|
+
console.error('Failed to write error message:', writeError);
|
|
749
|
+
}
|
|
750
|
+
connectionManager.safeCloseConnection(`session error: ${errorMessage}`);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
catch (error) {
|
|
755
|
+
console.error('Error in AI chat:', error);
|
|
756
|
+
// 使用安全关闭连接函数(如果在 try 块内部定义的话)
|
|
757
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
758
|
+
if (!res.headersSent) {
|
|
759
|
+
// 如果还没有设置为 SSE,返回 JSON 错误
|
|
760
|
+
res.status(500).json({ error: 'AI request failed', message: errorMessage });
|
|
761
|
+
}
|
|
762
|
+
else {
|
|
763
|
+
// 如果已经是 SSE 连接,发送错误事件并关闭
|
|
764
|
+
try {
|
|
765
|
+
if (!res.destroyed) {
|
|
766
|
+
res.write(`data: ${JSON.stringify({
|
|
767
|
+
type: 'error',
|
|
768
|
+
error: 'AI request failed',
|
|
769
|
+
message: errorMessage,
|
|
770
|
+
timestamp: Date.now()
|
|
771
|
+
})}\n\n`);
|
|
772
|
+
res.end();
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
catch (writeError) {
|
|
776
|
+
console.error('Failed to write final error message:', writeError);
|
|
777
|
+
try {
|
|
778
|
+
if (!res.destroyed) {
|
|
779
|
+
res.end();
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
catch (endError) {
|
|
783
|
+
console.error('Failed to end response in error handler:', endError);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
});
|
|
789
|
+
// Helper function to read MCP config (needed for chat functionality)
|
|
790
|
+
const readMcpConfig = () => {
|
|
791
|
+
const mcpConfigPath = path.join(os.homedir(), '.claude-agent', 'mcp-server.json');
|
|
792
|
+
if (fs.existsSync(mcpConfigPath)) {
|
|
793
|
+
try {
|
|
794
|
+
return JSON.parse(fs.readFileSync(mcpConfigPath, 'utf-8'));
|
|
795
|
+
}
|
|
796
|
+
catch (error) {
|
|
797
|
+
console.error('Failed to parse MCP configuration:', error);
|
|
798
|
+
return { mcpServers: {} };
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
return { mcpServers: {} };
|
|
802
|
+
};
|
|
803
|
+
export default router;
|
|
804
|
+
//# sourceMappingURL=agents.js.map
|