@cloudcli-ai/cloudcli 1.29.5 → 1.30.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/README.de.md +1 -1
- package/README.ja.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/README.ru.md +1 -1
- package/README.tr.md +252 -0
- package/README.zh-CN.md +1 -1
- package/dist/assets/index-DI22MZMb.css +32 -0
- package/dist/assets/index-RX-W_g_Q.js +1262 -0
- package/dist/index.html +2 -2
- package/dist-server/server/claude-sdk.js +13 -8
- package/dist-server/server/claude-sdk.js.map +1 -1
- package/dist-server/server/cursor-cli.js +7 -7
- package/dist-server/server/cursor-cli.js.map +1 -1
- package/dist-server/server/gemini-cli.js +5 -5
- package/dist-server/server/gemini-cli.js.map +1 -1
- package/dist-server/server/gemini-response-handler.js +2 -2
- package/dist-server/server/gemini-response-handler.js.map +1 -1
- package/dist-server/server/index.js +27 -25
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +103 -0
- package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude-mcp.provider.js +103 -0
- package/dist-server/server/modules/providers/list/claude/claude-mcp.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +266 -0
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude.provider.js +13 -0
- package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-auth.provider.js +84 -0
- package/dist-server/server/modules/providers/list/codex/codex-auth.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-mcp.provider.js +107 -0
- package/dist-server/server/modules/providers/list/codex/codex-mcp.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +282 -0
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex.provider.js +13 -0
- package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js +118 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-mcp.provider.js +80 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-mcp.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +369 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor.provider.js +13 -0
- package/dist-server/server/modules/providers/list/cursor/cursor.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js +131 -0
- package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/gemini/gemini-mcp.provider.js +82 -0
- package/dist-server/server/modules/providers/list/gemini/gemini-mcp.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js +207 -0
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/gemini/gemini.provider.js +13 -0
- package/dist-server/server/modules/providers/list/gemini/gemini.provider.js.map +1 -0
- package/dist-server/server/modules/providers/provider.registry.js +31 -0
- package/dist-server/server/modules/providers/provider.registry.js.map +1 -0
- package/dist-server/server/modules/providers/provider.routes.js +159 -0
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -0
- package/dist-server/server/modules/providers/services/mcp.service.js +69 -0
- package/dist-server/server/modules/providers/services/mcp.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/provider-auth.service.js +25 -0
- package/dist-server/server/modules/providers/services/provider-auth.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/sessions.service.js +29 -0
- package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -0
- package/dist-server/server/modules/providers/shared/base/abstract.provider.js +14 -0
- package/dist-server/server/modules/providers/shared/base/abstract.provider.js.map +1 -0
- package/dist-server/server/modules/providers/shared/mcp/mcp.provider.js +102 -0
- package/dist-server/server/modules/providers/shared/mcp/mcp.provider.js.map +1 -0
- package/dist-server/server/modules/providers/tests/mcp.test.js +250 -0
- package/dist-server/server/modules/providers/tests/mcp.test.js.map +1 -0
- package/dist-server/server/openai-codex.js +5 -5
- package/dist-server/server/openai-codex.js.map +1 -1
- package/dist-server/server/routes/codex.js +2 -278
- package/dist-server/server/routes/codex.js.map +1 -1
- package/dist-server/server/routes/commands.js +0 -44
- package/dist-server/server/routes/commands.js.map +1 -1
- package/dist-server/server/routes/cursor.js +9 -489
- package/dist-server/server/routes/cursor.js.map +1 -1
- package/dist-server/server/routes/mcp-utils.js +1 -18
- package/dist-server/server/routes/mcp-utils.js.map +1 -1
- package/dist-server/server/routes/messages.js +6 -6
- package/dist-server/server/routes/messages.js.map +1 -1
- package/dist-server/server/routes/settings.js +10 -0
- package/dist-server/server/routes/settings.js.map +1 -1
- package/dist-server/server/routes/taskmaster.js +0 -459
- package/dist-server/server/routes/taskmaster.js.map +1 -1
- package/dist-server/server/shared/interfaces.js +2 -0
- package/dist-server/server/shared/interfaces.js.map +1 -0
- package/dist-server/server/shared/types.js +3 -0
- package/dist-server/server/shared/types.js.map +1 -0
- package/dist-server/server/shared/utils.js +150 -0
- package/dist-server/server/shared/utils.js.map +1 -0
- package/dist-server/server/utils/mcp-detector.js +0 -49
- package/dist-server/server/utils/mcp-detector.js.map +1 -1
- package/dist-server/shared/modelConstants.js +1 -0
- package/dist-server/shared/modelConstants.js.map +1 -1
- package/package.json +5 -2
- package/server/claude-sdk.js +14 -8
- package/server/cursor-cli.js +7 -7
- package/server/gemini-cli.js +5 -5
- package/server/gemini-response-handler.js +2 -2
- package/server/index.js +33 -29
- package/server/modules/providers/list/claude/claude-auth.provider.ts +123 -0
- package/server/modules/providers/list/claude/claude-mcp.provider.ts +135 -0
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +306 -0
- package/server/modules/providers/list/claude/claude.provider.ts +15 -0
- package/server/modules/providers/list/codex/codex-auth.provider.ts +100 -0
- package/server/modules/providers/list/codex/codex-mcp.provider.ts +135 -0
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +319 -0
- package/server/modules/providers/list/codex/codex.provider.ts +15 -0
- package/server/modules/providers/list/cursor/cursor-auth.provider.ts +143 -0
- package/server/modules/providers/list/cursor/cursor-mcp.provider.ts +108 -0
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +421 -0
- package/server/modules/providers/list/cursor/cursor.provider.ts +15 -0
- package/server/modules/providers/list/gemini/gemini-auth.provider.ts +151 -0
- package/server/modules/providers/list/gemini/gemini-mcp.provider.ts +110 -0
- package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +227 -0
- package/server/modules/providers/list/gemini/gemini.provider.ts +15 -0
- package/server/modules/providers/provider.registry.ts +36 -0
- package/server/modules/providers/provider.routes.ts +217 -0
- package/server/modules/providers/services/mcp.service.ts +94 -0
- package/server/modules/providers/services/provider-auth.service.ts +26 -0
- package/server/modules/providers/services/sessions.service.ts +45 -0
- package/server/modules/providers/shared/base/abstract.provider.ts +20 -0
- package/server/modules/providers/shared/mcp/mcp.provider.ts +151 -0
- package/server/modules/providers/tests/mcp.test.ts +293 -0
- package/server/openai-codex.js +5 -5
- package/server/routes/codex.js +2 -312
- package/server/routes/commands.js +0 -49
- package/server/routes/cursor.js +15 -527
- package/server/routes/mcp-utils.js +2 -19
- package/server/routes/messages.js +6 -6
- package/server/routes/settings.js +10 -0
- package/server/routes/taskmaster.js +0 -492
- package/server/shared/interfaces.ts +54 -0
- package/server/shared/types.ts +172 -0
- package/server/shared/utils.ts +193 -0
- package/server/tsconfig.json +6 -3
- package/server/utils/mcp-detector.js +0 -51
- package/shared/modelConstants.js +1 -0
- package/dist/assets/index-BBAE1OJ_.css +0 -32
- package/dist/assets/index-Ci1GOyV7.js +0 -1262
- package/dist-server/server/providers/claude/adapter.js +0 -266
- package/dist-server/server/providers/claude/adapter.js.map +0 -1
- package/dist-server/server/providers/claude/status.js +0 -121
- package/dist-server/server/providers/claude/status.js.map +0 -1
- package/dist-server/server/providers/codex/adapter.js +0 -232
- package/dist-server/server/providers/codex/adapter.js.map +0 -1
- package/dist-server/server/providers/codex/status.js +0 -70
- package/dist-server/server/providers/codex/status.js.map +0 -1
- package/dist-server/server/providers/cursor/adapter.js +0 -342
- package/dist-server/server/providers/cursor/adapter.js.map +0 -1
- package/dist-server/server/providers/cursor/status.js +0 -118
- package/dist-server/server/providers/cursor/status.js.map +0 -1
- package/dist-server/server/providers/gemini/adapter.js +0 -174
- package/dist-server/server/providers/gemini/adapter.js.map +0 -1
- package/dist-server/server/providers/gemini/status.js +0 -104
- package/dist-server/server/providers/gemini/status.js.map +0 -1
- package/dist-server/server/providers/registry.js +0 -58
- package/dist-server/server/providers/registry.js.map +0 -1
- package/dist-server/server/providers/types.js +0 -117
- package/dist-server/server/providers/types.js.map +0 -1
- package/dist-server/server/providers/utils.js +0 -28
- package/dist-server/server/providers/utils.js.map +0 -1
- package/dist-server/server/routes/cli-auth.js +0 -25
- package/dist-server/server/routes/cli-auth.js.map +0 -1
- package/dist-server/server/routes/mcp.js +0 -480
- package/dist-server/server/routes/mcp.js.map +0 -1
- package/server/providers/claude/adapter.js +0 -278
- package/server/providers/claude/status.js +0 -136
- package/server/providers/codex/adapter.js +0 -248
- package/server/providers/codex/status.js +0 -78
- package/server/providers/cursor/adapter.js +0 -348
- package/server/providers/cursor/status.js +0 -128
- package/server/providers/gemini/adapter.js +0 -186
- package/server/providers/gemini/status.js +0 -111
- package/server/providers/registry.js +0 -67
- package/server/providers/types.js +0 -132
- package/server/providers/utils.js +0 -29
- package/server/routes/cli-auth.js +0 -27
- package/server/routes/mcp.js +0 -552
package/server/index.js
CHANGED
|
@@ -5,6 +5,9 @@ import fs from 'fs';
|
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import { findAppRoot, getModuleDir } from './utils/runtime-paths.js';
|
|
7
7
|
|
|
8
|
+
import { AppError, createNormalizedMessage } from '@/shared/utils.js';
|
|
9
|
+
|
|
10
|
+
|
|
8
11
|
const __dirname = getModuleDir(import.meta.url);
|
|
9
12
|
// The server source runs from /server, while the compiled output runs from /dist-server/server.
|
|
10
13
|
// Resolving the app root once keeps every repo-level lookup below aligned across both layouts.
|
|
@@ -23,10 +26,9 @@ import cors from 'cors';
|
|
|
23
26
|
import { promises as fsPromises } from 'fs';
|
|
24
27
|
import { spawn } from 'child_process';
|
|
25
28
|
import pty from 'node-pty';
|
|
26
|
-
import fetch from 'node-fetch';
|
|
27
29
|
import mime from 'mime-types';
|
|
28
30
|
|
|
29
|
-
import { getProjects, getSessions, renameProject, deleteSession, deleteProject,
|
|
31
|
+
import { getProjects, getSessions, renameProject, deleteSession, deleteProject, extractProjectDirectory, clearProjectDirectoryCache, searchConversations } from './projects.js';
|
|
30
32
|
import { queryClaudeSDK, abortClaudeSDKSession, isClaudeSDKSessionActive, getActiveClaudeSDKSessions, resolveToolApproval, getPendingApprovalsForSession, reconnectSessionWriter } from './claude-sdk.js';
|
|
31
33
|
import { spawnCursor, abortCursorSession, isCursorSessionActive, getActiveCursorSessions } from './cursor-cli.js';
|
|
32
34
|
import { queryCodex, abortCodexSession, isCodexSessionActive, getActiveCodexSessions } from './openai-codex.js';
|
|
@@ -34,7 +36,6 @@ import { spawnGemini, abortGeminiSession, isGeminiSessionActive, getActiveGemini
|
|
|
34
36
|
import sessionManager from './sessionManager.js';
|
|
35
37
|
import gitRoutes from './routes/git.js';
|
|
36
38
|
import authRoutes from './routes/auth.js';
|
|
37
|
-
import mcpRoutes from './routes/mcp.js';
|
|
38
39
|
import cursorRoutes from './routes/cursor.js';
|
|
39
40
|
import taskmasterRoutes from './routes/taskmaster.js';
|
|
40
41
|
import mcpUtilsRoutes from './routes/mcp-utils.js';
|
|
@@ -42,13 +43,12 @@ import commandsRoutes from './routes/commands.js';
|
|
|
42
43
|
import settingsRoutes from './routes/settings.js';
|
|
43
44
|
import agentRoutes from './routes/agent.js';
|
|
44
45
|
import projectsRoutes, { WORKSPACES_ROOT, validateWorkspacePath } from './routes/projects.js';
|
|
45
|
-
import cliAuthRoutes from './routes/cli-auth.js';
|
|
46
46
|
import userRoutes from './routes/user.js';
|
|
47
47
|
import codexRoutes from './routes/codex.js';
|
|
48
48
|
import geminiRoutes from './routes/gemini.js';
|
|
49
49
|
import pluginsRoutes from './routes/plugins.js';
|
|
50
50
|
import messagesRoutes from './routes/messages.js';
|
|
51
|
-
import
|
|
51
|
+
import providerRoutes from './modules/providers/provider.routes.js';
|
|
52
52
|
import { startEnabledPluginServers, stopAllPlugins, getPluginPort } from './utils/plugin-process-manager.js';
|
|
53
53
|
import { initializeDatabase, sessionNamesDb, applyCustomSessionNames } from './database/db.js';
|
|
54
54
|
import { configureWebPush } from './services/vapid-keys.js';
|
|
@@ -286,9 +286,6 @@ app.use('/api/projects', authenticateToken, projectsRoutes);
|
|
|
286
286
|
// Git API Routes (protected)
|
|
287
287
|
app.use('/api/git', authenticateToken, gitRoutes);
|
|
288
288
|
|
|
289
|
-
// MCP API Routes (protected)
|
|
290
|
-
app.use('/api/mcp', authenticateToken, mcpRoutes);
|
|
291
|
-
|
|
292
289
|
// Cursor API Routes (protected)
|
|
293
290
|
app.use('/api/cursor', authenticateToken, cursorRoutes);
|
|
294
291
|
|
|
@@ -304,9 +301,6 @@ app.use('/api/commands', authenticateToken, commandsRoutes);
|
|
|
304
301
|
// Settings API Routes (protected)
|
|
305
302
|
app.use('/api/settings', authenticateToken, settingsRoutes);
|
|
306
303
|
|
|
307
|
-
// CLI Authentication API Routes (protected)
|
|
308
|
-
app.use('/api/cli', authenticateToken, cliAuthRoutes);
|
|
309
|
-
|
|
310
304
|
// User API Routes (protected)
|
|
311
305
|
app.use('/api/user', authenticateToken, userRoutes);
|
|
312
306
|
|
|
@@ -322,6 +316,9 @@ app.use('/api/plugins', authenticateToken, pluginsRoutes);
|
|
|
322
316
|
// Unified session messages route (protected)
|
|
323
317
|
app.use('/api/sessions', authenticateToken, messagesRoutes);
|
|
324
318
|
|
|
319
|
+
// Unified provider MCP routes (protected)
|
|
320
|
+
app.use('/api/providers', authenticateToken, providerRoutes);
|
|
321
|
+
|
|
325
322
|
// Agent API Routes (uses API key authentication)
|
|
326
323
|
app.use('/api/agent', agentRoutes);
|
|
327
324
|
|
|
@@ -509,23 +506,6 @@ app.delete('/api/projects/:projectName', authenticateToken, async (req, res) =>
|
|
|
509
506
|
}
|
|
510
507
|
});
|
|
511
508
|
|
|
512
|
-
// Create project endpoint
|
|
513
|
-
app.post('/api/projects/create', authenticateToken, async (req, res) => {
|
|
514
|
-
try {
|
|
515
|
-
const { path: projectPath } = req.body;
|
|
516
|
-
|
|
517
|
-
if (!projectPath || !projectPath.trim()) {
|
|
518
|
-
return res.status(400).json({ error: 'Project path is required' });
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
const project = await addProjectManually(projectPath.trim());
|
|
522
|
-
res.json({ success: true, project });
|
|
523
|
-
} catch (error) {
|
|
524
|
-
console.error('Error creating project:', error);
|
|
525
|
-
res.status(500).json({ error: error.message });
|
|
526
|
-
}
|
|
527
|
-
});
|
|
528
|
-
|
|
529
509
|
// Search conversations content (SSE streaming)
|
|
530
510
|
app.get('/api/search/conversations', authenticateToken, async (req, res) => {
|
|
531
511
|
const query = typeof req.query.q === 'string' ? req.query.q.trim() : '';
|
|
@@ -1378,7 +1358,7 @@ wss.on('connection', (ws, request) => {
|
|
|
1378
1358
|
/**
|
|
1379
1359
|
* WebSocket Writer - Wrapper for WebSocket to match SSEStreamWriter interface
|
|
1380
1360
|
*
|
|
1381
|
-
* Provider files use `createNormalizedMessage()` from `
|
|
1361
|
+
* Provider files use `createNormalizedMessage()` from `shared/utils.js` and
|
|
1382
1362
|
* adapter `normalizeMessage()` to produce unified NormalizedMessage events.
|
|
1383
1363
|
* The writer simply serialises and sends.
|
|
1384
1364
|
*/
|
|
@@ -2213,6 +2193,30 @@ app.get('*', (req, res) => {
|
|
|
2213
2193
|
}
|
|
2214
2194
|
});
|
|
2215
2195
|
|
|
2196
|
+
// global error middleware must be last
|
|
2197
|
+
app.use((err, req, res, next) => {
|
|
2198
|
+
if (err instanceof AppError) {
|
|
2199
|
+
return res.status(err.statusCode).json({
|
|
2200
|
+
success: false,
|
|
2201
|
+
error: {
|
|
2202
|
+
code: err.code,
|
|
2203
|
+
message: err.message,
|
|
2204
|
+
details: err.details,
|
|
2205
|
+
},
|
|
2206
|
+
});
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
console.error(err);
|
|
2210
|
+
|
|
2211
|
+
return res.status(500).json({
|
|
2212
|
+
success: false,
|
|
2213
|
+
error: {
|
|
2214
|
+
code: 'INTERNAL_ERROR',
|
|
2215
|
+
message: 'Internal server error',
|
|
2216
|
+
},
|
|
2217
|
+
});
|
|
2218
|
+
});
|
|
2219
|
+
|
|
2216
2220
|
// Helper function to convert permissions to rwx format
|
|
2217
2221
|
function permToRwx(perm) {
|
|
2218
2222
|
const r = perm & 4 ? 'r' : '-';
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import spawn from 'cross-spawn';
|
|
6
|
+
|
|
7
|
+
import type { IProviderAuth } from '@/shared/interfaces.js';
|
|
8
|
+
import type { ProviderAuthStatus } from '@/shared/types.js';
|
|
9
|
+
import { readObjectRecord, readOptionalString } from '@/shared/utils.js';
|
|
10
|
+
|
|
11
|
+
type ClaudeCredentialsStatus = {
|
|
12
|
+
authenticated: boolean;
|
|
13
|
+
email: string | null;
|
|
14
|
+
method: string | null;
|
|
15
|
+
error?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export class ClaudeProviderAuth implements IProviderAuth {
|
|
19
|
+
/**
|
|
20
|
+
* Checks whether the Claude Code CLI is available on this host.
|
|
21
|
+
*/
|
|
22
|
+
private checkInstalled(): boolean {
|
|
23
|
+
const cliPath = process.env.CLAUDE_CLI_PATH || 'claude';
|
|
24
|
+
try {
|
|
25
|
+
spawn.sync(cliPath, ['--version'], { stdio: 'ignore', timeout: 5000 });
|
|
26
|
+
return true;
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Returns Claude installation and credential status using Claude Code's auth priority.
|
|
34
|
+
*/
|
|
35
|
+
async getStatus(): Promise<ProviderAuthStatus> {
|
|
36
|
+
const installed = this.checkInstalled();
|
|
37
|
+
|
|
38
|
+
if (!installed) {
|
|
39
|
+
return {
|
|
40
|
+
installed,
|
|
41
|
+
provider: 'claude',
|
|
42
|
+
authenticated: false,
|
|
43
|
+
email: null,
|
|
44
|
+
method: null,
|
|
45
|
+
error: 'Claude Code CLI is not installed',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const credentials = await this.checkCredentials();
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
installed,
|
|
53
|
+
provider: 'claude',
|
|
54
|
+
authenticated: credentials.authenticated,
|
|
55
|
+
email: credentials.authenticated ? credentials.email || 'Authenticated' : credentials.email,
|
|
56
|
+
method: credentials.method,
|
|
57
|
+
error: credentials.authenticated ? undefined : credentials.error || 'Not authenticated',
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Reads Claude settings env values that the CLI can use even when the server process env is empty.
|
|
63
|
+
*/
|
|
64
|
+
private async loadSettingsEnv(): Promise<Record<string, unknown>> {
|
|
65
|
+
try {
|
|
66
|
+
const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
67
|
+
const content = await readFile(settingsPath, 'utf8');
|
|
68
|
+
const settings = readObjectRecord(JSON.parse(content));
|
|
69
|
+
return readObjectRecord(settings?.env) ?? {};
|
|
70
|
+
} catch {
|
|
71
|
+
return {};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Checks Claude credentials in the same priority order used by Claude Code.
|
|
77
|
+
*/
|
|
78
|
+
private async checkCredentials(): Promise<ClaudeCredentialsStatus> {
|
|
79
|
+
if (process.env.ANTHROPIC_API_KEY?.trim()) {
|
|
80
|
+
return { authenticated: true, email: 'API Key Auth', method: 'api_key' };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const settingsEnv = await this.loadSettingsEnv();
|
|
84
|
+
if (readOptionalString(settingsEnv.ANTHROPIC_API_KEY)) {
|
|
85
|
+
return { authenticated: true, email: 'API Key Auth', method: 'api_key' };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (readOptionalString(settingsEnv.ANTHROPIC_AUTH_TOKEN)) {
|
|
89
|
+
return { authenticated: true, email: 'Configured via settings.json', method: 'api_key' };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
const credPath = path.join(os.homedir(), '.claude', '.credentials.json');
|
|
94
|
+
const content = await readFile(credPath, 'utf8');
|
|
95
|
+
const creds = readObjectRecord(JSON.parse(content)) ?? {};
|
|
96
|
+
const oauth = readObjectRecord(creds.claudeAiOauth);
|
|
97
|
+
const accessToken = readOptionalString(oauth?.accessToken);
|
|
98
|
+
|
|
99
|
+
if (accessToken) {
|
|
100
|
+
const expiresAt = typeof oauth?.expiresAt === 'number' ? oauth.expiresAt : undefined;
|
|
101
|
+
const email = readOptionalString(creds.email) ?? readOptionalString(creds.user) ?? null;
|
|
102
|
+
if (!expiresAt || Date.now() < expiresAt) {
|
|
103
|
+
return {
|
|
104
|
+
authenticated: true,
|
|
105
|
+
email,
|
|
106
|
+
method: 'credentials_file',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
authenticated: false,
|
|
112
|
+
email,
|
|
113
|
+
method: 'credentials_file',
|
|
114
|
+
error: 'OAuth token has expired. Please re-authenticate with claude login',
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { authenticated: false, email: null, method: null };
|
|
119
|
+
} catch {
|
|
120
|
+
return { authenticated: false, email: null, method: null };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { McpProvider } from '@/modules/providers/shared/mcp/mcp.provider.js';
|
|
5
|
+
import type { McpScope, ProviderMcpServer, UpsertProviderMcpServerInput } from '@/shared/types.js';
|
|
6
|
+
import {
|
|
7
|
+
AppError,
|
|
8
|
+
readJsonConfig,
|
|
9
|
+
readObjectRecord,
|
|
10
|
+
readOptionalString,
|
|
11
|
+
readStringArray,
|
|
12
|
+
readStringRecord,
|
|
13
|
+
writeJsonConfig,
|
|
14
|
+
} from '@/shared/utils.js';
|
|
15
|
+
|
|
16
|
+
export class ClaudeMcpProvider extends McpProvider {
|
|
17
|
+
constructor() {
|
|
18
|
+
super('claude', ['user', 'local', 'project'], ['stdio', 'http', 'sse']);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
protected async readScopedServers(scope: McpScope, workspacePath: string): Promise<Record<string, unknown>> {
|
|
22
|
+
if (scope === 'project') {
|
|
23
|
+
const filePath = path.join(workspacePath, '.mcp.json');
|
|
24
|
+
const config = await readJsonConfig(filePath);
|
|
25
|
+
return readObjectRecord(config.mcpServers) ?? {};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const filePath = path.join(os.homedir(), '.claude.json');
|
|
29
|
+
const config = await readJsonConfig(filePath);
|
|
30
|
+
if (scope === 'user') {
|
|
31
|
+
return readObjectRecord(config.mcpServers) ?? {};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const projects = readObjectRecord(config.projects) ?? {};
|
|
35
|
+
const projectConfig = readObjectRecord(projects[workspacePath]) ?? {};
|
|
36
|
+
return readObjectRecord(projectConfig.mcpServers) ?? {};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
protected async writeScopedServers(
|
|
40
|
+
scope: McpScope,
|
|
41
|
+
workspacePath: string,
|
|
42
|
+
servers: Record<string, unknown>,
|
|
43
|
+
): Promise<void> {
|
|
44
|
+
if (scope === 'project') {
|
|
45
|
+
const filePath = path.join(workspacePath, '.mcp.json');
|
|
46
|
+
const config = await readJsonConfig(filePath);
|
|
47
|
+
config.mcpServers = servers;
|
|
48
|
+
await writeJsonConfig(filePath, config);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const filePath = path.join(os.homedir(), '.claude.json');
|
|
53
|
+
const config = await readJsonConfig(filePath);
|
|
54
|
+
if (scope === 'user') {
|
|
55
|
+
config.mcpServers = servers;
|
|
56
|
+
await writeJsonConfig(filePath, config);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const projects = readObjectRecord(config.projects) ?? {};
|
|
61
|
+
const projectConfig = readObjectRecord(projects[workspacePath]) ?? {};
|
|
62
|
+
projectConfig.mcpServers = servers;
|
|
63
|
+
projects[workspacePath] = projectConfig;
|
|
64
|
+
config.projects = projects;
|
|
65
|
+
await writeJsonConfig(filePath, config);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
protected buildServerConfig(input: UpsertProviderMcpServerInput): Record<string, unknown> {
|
|
69
|
+
if (input.transport === 'stdio') {
|
|
70
|
+
if (!input.command?.trim()) {
|
|
71
|
+
throw new AppError('command is required for stdio MCP servers.', {
|
|
72
|
+
code: 'MCP_COMMAND_REQUIRED',
|
|
73
|
+
statusCode: 400,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
type: 'stdio',
|
|
79
|
+
command: input.command,
|
|
80
|
+
args: input.args ?? [],
|
|
81
|
+
env: input.env ?? {},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!input.url?.trim()) {
|
|
86
|
+
throw new AppError('url is required for http/sse MCP servers.', {
|
|
87
|
+
code: 'MCP_URL_REQUIRED',
|
|
88
|
+
statusCode: 400,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
type: input.transport,
|
|
94
|
+
url: input.url,
|
|
95
|
+
headers: input.headers ?? {},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
protected normalizeServerConfig(
|
|
100
|
+
scope: McpScope,
|
|
101
|
+
name: string,
|
|
102
|
+
rawConfig: unknown,
|
|
103
|
+
): ProviderMcpServer | null {
|
|
104
|
+
if (!rawConfig || typeof rawConfig !== 'object') {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const config = rawConfig as Record<string, unknown>;
|
|
109
|
+
if (typeof config.command === 'string') {
|
|
110
|
+
return {
|
|
111
|
+
provider: 'claude',
|
|
112
|
+
name,
|
|
113
|
+
scope,
|
|
114
|
+
transport: 'stdio',
|
|
115
|
+
command: config.command,
|
|
116
|
+
args: readStringArray(config.args),
|
|
117
|
+
env: readStringRecord(config.env),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (typeof config.url === 'string') {
|
|
122
|
+
const transport = readOptionalString(config.type) === 'sse' ? 'sse' : 'http';
|
|
123
|
+
return {
|
|
124
|
+
provider: 'claude',
|
|
125
|
+
name,
|
|
126
|
+
scope,
|
|
127
|
+
transport,
|
|
128
|
+
url: config.url,
|
|
129
|
+
headers: readStringRecord(config.headers),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { getSessionMessages } from '@/projects.js';
|
|
2
|
+
import type { IProviderSessions } from '@/shared/interfaces.js';
|
|
3
|
+
import type { AnyRecord, FetchHistoryOptions, FetchHistoryResult, NormalizedMessage } from '@/shared/types.js';
|
|
4
|
+
import { createNormalizedMessage, generateMessageId, readObjectRecord } from '@/shared/utils.js';
|
|
5
|
+
|
|
6
|
+
const PROVIDER = 'claude';
|
|
7
|
+
|
|
8
|
+
type ClaudeToolResult = {
|
|
9
|
+
content: unknown;
|
|
10
|
+
isError: boolean;
|
|
11
|
+
subagentTools?: unknown;
|
|
12
|
+
toolUseResult?: unknown;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type ClaudeHistoryResult =
|
|
16
|
+
| AnyRecord[]
|
|
17
|
+
| {
|
|
18
|
+
messages?: AnyRecord[];
|
|
19
|
+
total?: number;
|
|
20
|
+
hasMore?: boolean;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const loadClaudeSessionMessages = getSessionMessages as unknown as (
|
|
24
|
+
projectName: string,
|
|
25
|
+
sessionId: string,
|
|
26
|
+
limit: number | null,
|
|
27
|
+
offset: number,
|
|
28
|
+
) => Promise<ClaudeHistoryResult>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Claude writes internal command and system reminder entries into history.
|
|
32
|
+
* Those are useful for the CLI but should not appear in the user-facing chat.
|
|
33
|
+
*/
|
|
34
|
+
const INTERNAL_CONTENT_PREFIXES = [
|
|
35
|
+
'<command-name>',
|
|
36
|
+
'<command-message>',
|
|
37
|
+
'<command-args>',
|
|
38
|
+
'<local-command-stdout>',
|
|
39
|
+
'<system-reminder>',
|
|
40
|
+
'Caveat:',
|
|
41
|
+
'This session is being continued from a previous',
|
|
42
|
+
'[Request interrupted',
|
|
43
|
+
] as const;
|
|
44
|
+
|
|
45
|
+
function isInternalContent(content: string): boolean {
|
|
46
|
+
return INTERNAL_CONTENT_PREFIXES.some((prefix) => content.startsWith(prefix));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class ClaudeSessionsProvider implements IProviderSessions {
|
|
50
|
+
/**
|
|
51
|
+
* Normalizes one Claude JSONL entry or live SDK stream event into the shared
|
|
52
|
+
* message shape consumed by REST and WebSocket clients.
|
|
53
|
+
*/
|
|
54
|
+
normalizeMessage(rawMessage: unknown, sessionId: string | null): NormalizedMessage[] {
|
|
55
|
+
const raw = readObjectRecord(rawMessage);
|
|
56
|
+
if (!raw) {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (raw.type === 'content_block_delta' && raw.delta?.text) {
|
|
61
|
+
return [createNormalizedMessage({ kind: 'stream_delta', content: raw.delta.text, sessionId, provider: PROVIDER })];
|
|
62
|
+
}
|
|
63
|
+
if (raw.type === 'content_block_stop') {
|
|
64
|
+
return [createNormalizedMessage({ kind: 'stream_end', sessionId, provider: PROVIDER })];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const messages: NormalizedMessage[] = [];
|
|
68
|
+
const ts = raw.timestamp || new Date().toISOString();
|
|
69
|
+
const baseId = raw.uuid || generateMessageId('claude');
|
|
70
|
+
|
|
71
|
+
if (raw.message?.role === 'user' && raw.message?.content) {
|
|
72
|
+
if (Array.isArray(raw.message.content)) {
|
|
73
|
+
for (let partIndex = 0; partIndex < raw.message.content.length; partIndex++) {
|
|
74
|
+
const part = raw.message.content[partIndex];
|
|
75
|
+
if (part.type === 'tool_result') {
|
|
76
|
+
messages.push(createNormalizedMessage({
|
|
77
|
+
id: `${baseId}_tr_${part.tool_use_id}`,
|
|
78
|
+
sessionId,
|
|
79
|
+
timestamp: ts,
|
|
80
|
+
provider: PROVIDER,
|
|
81
|
+
kind: 'tool_result',
|
|
82
|
+
toolId: part.tool_use_id,
|
|
83
|
+
content: typeof part.content === 'string' ? part.content : JSON.stringify(part.content),
|
|
84
|
+
isError: Boolean(part.is_error),
|
|
85
|
+
subagentTools: raw.subagentTools,
|
|
86
|
+
toolUseResult: raw.toolUseResult,
|
|
87
|
+
}));
|
|
88
|
+
} else if (part.type === 'text') {
|
|
89
|
+
const text = part.text || '';
|
|
90
|
+
if (text && !isInternalContent(text)) {
|
|
91
|
+
messages.push(createNormalizedMessage({
|
|
92
|
+
id: `${baseId}_text_${partIndex}`,
|
|
93
|
+
sessionId,
|
|
94
|
+
timestamp: ts,
|
|
95
|
+
provider: PROVIDER,
|
|
96
|
+
kind: 'text',
|
|
97
|
+
role: 'user',
|
|
98
|
+
content: text,
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (messages.length === 0) {
|
|
105
|
+
const textParts = raw.message.content
|
|
106
|
+
.filter((part: AnyRecord) => part.type === 'text')
|
|
107
|
+
.map((part: AnyRecord) => part.text)
|
|
108
|
+
.filter(Boolean)
|
|
109
|
+
.join('\n');
|
|
110
|
+
if (textParts && !isInternalContent(textParts)) {
|
|
111
|
+
messages.push(createNormalizedMessage({
|
|
112
|
+
id: `${baseId}_text`,
|
|
113
|
+
sessionId,
|
|
114
|
+
timestamp: ts,
|
|
115
|
+
provider: PROVIDER,
|
|
116
|
+
kind: 'text',
|
|
117
|
+
role: 'user',
|
|
118
|
+
content: textParts,
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} else if (typeof raw.message.content === 'string') {
|
|
123
|
+
const text = raw.message.content;
|
|
124
|
+
if (text && !isInternalContent(text)) {
|
|
125
|
+
messages.push(createNormalizedMessage({
|
|
126
|
+
id: baseId,
|
|
127
|
+
sessionId,
|
|
128
|
+
timestamp: ts,
|
|
129
|
+
provider: PROVIDER,
|
|
130
|
+
kind: 'text',
|
|
131
|
+
role: 'user',
|
|
132
|
+
content: text,
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return messages;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (raw.type === 'thinking' && raw.message?.content) {
|
|
140
|
+
messages.push(createNormalizedMessage({
|
|
141
|
+
id: baseId,
|
|
142
|
+
sessionId,
|
|
143
|
+
timestamp: ts,
|
|
144
|
+
provider: PROVIDER,
|
|
145
|
+
kind: 'thinking',
|
|
146
|
+
content: raw.message.content,
|
|
147
|
+
}));
|
|
148
|
+
return messages;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (raw.type === 'tool_use' && raw.toolName) {
|
|
152
|
+
messages.push(createNormalizedMessage({
|
|
153
|
+
id: baseId,
|
|
154
|
+
sessionId,
|
|
155
|
+
timestamp: ts,
|
|
156
|
+
provider: PROVIDER,
|
|
157
|
+
kind: 'tool_use',
|
|
158
|
+
toolName: raw.toolName,
|
|
159
|
+
toolInput: raw.toolInput,
|
|
160
|
+
toolId: raw.toolCallId || baseId,
|
|
161
|
+
}));
|
|
162
|
+
return messages;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (raw.type === 'tool_result') {
|
|
166
|
+
messages.push(createNormalizedMessage({
|
|
167
|
+
id: baseId,
|
|
168
|
+
sessionId,
|
|
169
|
+
timestamp: ts,
|
|
170
|
+
provider: PROVIDER,
|
|
171
|
+
kind: 'tool_result',
|
|
172
|
+
toolId: raw.toolCallId || '',
|
|
173
|
+
content: raw.output || '',
|
|
174
|
+
isError: false,
|
|
175
|
+
}));
|
|
176
|
+
return messages;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (raw.message?.role === 'assistant' && raw.message?.content) {
|
|
180
|
+
if (Array.isArray(raw.message.content)) {
|
|
181
|
+
let partIndex = 0;
|
|
182
|
+
for (const part of raw.message.content) {
|
|
183
|
+
if (part.type === 'text' && part.text) {
|
|
184
|
+
messages.push(createNormalizedMessage({
|
|
185
|
+
id: `${baseId}_${partIndex}`,
|
|
186
|
+
sessionId,
|
|
187
|
+
timestamp: ts,
|
|
188
|
+
provider: PROVIDER,
|
|
189
|
+
kind: 'text',
|
|
190
|
+
role: 'assistant',
|
|
191
|
+
content: part.text,
|
|
192
|
+
}));
|
|
193
|
+
} else if (part.type === 'tool_use') {
|
|
194
|
+
messages.push(createNormalizedMessage({
|
|
195
|
+
id: `${baseId}_${partIndex}`,
|
|
196
|
+
sessionId,
|
|
197
|
+
timestamp: ts,
|
|
198
|
+
provider: PROVIDER,
|
|
199
|
+
kind: 'tool_use',
|
|
200
|
+
toolName: part.name,
|
|
201
|
+
toolInput: part.input,
|
|
202
|
+
toolId: part.id,
|
|
203
|
+
}));
|
|
204
|
+
} else if (part.type === 'thinking' && part.thinking) {
|
|
205
|
+
messages.push(createNormalizedMessage({
|
|
206
|
+
id: `${baseId}_${partIndex}`,
|
|
207
|
+
sessionId,
|
|
208
|
+
timestamp: ts,
|
|
209
|
+
provider: PROVIDER,
|
|
210
|
+
kind: 'thinking',
|
|
211
|
+
content: part.thinking,
|
|
212
|
+
}));
|
|
213
|
+
}
|
|
214
|
+
partIndex++;
|
|
215
|
+
}
|
|
216
|
+
} else if (typeof raw.message.content === 'string') {
|
|
217
|
+
messages.push(createNormalizedMessage({
|
|
218
|
+
id: baseId,
|
|
219
|
+
sessionId,
|
|
220
|
+
timestamp: ts,
|
|
221
|
+
provider: PROVIDER,
|
|
222
|
+
kind: 'text',
|
|
223
|
+
role: 'assistant',
|
|
224
|
+
content: raw.message.content,
|
|
225
|
+
}));
|
|
226
|
+
}
|
|
227
|
+
return messages;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return messages;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Loads Claude JSONL history for a project/session and returns normalized
|
|
235
|
+
* messages, preserving the existing pagination behavior from projects.js.
|
|
236
|
+
*/
|
|
237
|
+
async fetchHistory(
|
|
238
|
+
sessionId: string,
|
|
239
|
+
options: FetchHistoryOptions = {},
|
|
240
|
+
): Promise<FetchHistoryResult> {
|
|
241
|
+
const { projectName, limit = null, offset = 0 } = options;
|
|
242
|
+
if (!projectName) {
|
|
243
|
+
return { messages: [], total: 0, hasMore: false, offset: 0, limit: null };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let result: ClaudeHistoryResult;
|
|
247
|
+
try {
|
|
248
|
+
result = await loadClaudeSessionMessages(projectName, sessionId, limit, offset);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
251
|
+
console.warn(`[ClaudeProvider] Failed to load session ${sessionId}:`, message);
|
|
252
|
+
return { messages: [], total: 0, hasMore: false, offset: 0, limit: null };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const rawMessages = Array.isArray(result) ? result : (result.messages || []);
|
|
256
|
+
const total = Array.isArray(result) ? rawMessages.length : (result.total || 0);
|
|
257
|
+
const hasMore = Array.isArray(result) ? false : Boolean(result.hasMore);
|
|
258
|
+
|
|
259
|
+
const toolResultMap = new Map<string, ClaudeToolResult>();
|
|
260
|
+
for (const raw of rawMessages) {
|
|
261
|
+
if (raw.message?.role === 'user' && Array.isArray(raw.message?.content)) {
|
|
262
|
+
for (const part of raw.message.content) {
|
|
263
|
+
if (part.type === 'tool_result' && part.tool_use_id) {
|
|
264
|
+
toolResultMap.set(part.tool_use_id, {
|
|
265
|
+
content: part.content,
|
|
266
|
+
isError: Boolean(part.is_error),
|
|
267
|
+
subagentTools: raw.subagentTools,
|
|
268
|
+
toolUseResult: raw.toolUseResult,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const normalized: NormalizedMessage[] = [];
|
|
276
|
+
for (const raw of rawMessages) {
|
|
277
|
+
normalized.push(...this.normalizeMessage(raw, sessionId));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
for (const msg of normalized) {
|
|
281
|
+
if (msg.kind === 'tool_use' && msg.toolId && toolResultMap.has(msg.toolId)) {
|
|
282
|
+
const toolResult = toolResultMap.get(msg.toolId);
|
|
283
|
+
if (!toolResult) {
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
msg.toolResult = {
|
|
288
|
+
content: typeof toolResult.content === 'string'
|
|
289
|
+
? toolResult.content
|
|
290
|
+
: JSON.stringify(toolResult.content),
|
|
291
|
+
isError: toolResult.isError,
|
|
292
|
+
toolUseResult: toolResult.toolUseResult,
|
|
293
|
+
};
|
|
294
|
+
msg.subagentTools = toolResult.subagentTools;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
messages: normalized,
|
|
300
|
+
total,
|
|
301
|
+
hasMore,
|
|
302
|
+
offset,
|
|
303
|
+
limit,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
}
|