@juspay/neurolink 9.87.3 → 9.88.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/CHANGELOG.md +17 -0
- package/dist/browser/neurolink.min.js +381 -381
- package/dist/cli/commands/proxy.d.ts +16 -1
- package/dist/cli/commands/proxy.js +275 -238
- package/dist/cli/factories/commandFactory.d.ts +39 -0
- package/dist/cli/factories/commandFactory.js +484 -3
- package/dist/core/conversationMemoryManager.d.ts +11 -1
- package/dist/core/conversationMemoryManager.js +44 -0
- package/dist/core/redisConversationMemoryManager.d.ts +12 -1
- package/dist/core/redisConversationMemoryManager.js +80 -0
- package/dist/lib/core/conversationMemoryManager.d.ts +11 -1
- package/dist/lib/core/conversationMemoryManager.js +44 -0
- package/dist/lib/core/redisConversationMemoryManager.d.ts +12 -1
- package/dist/lib/core/redisConversationMemoryManager.js +80 -0
- package/dist/lib/neurolink.d.ts +27 -0
- package/dist/lib/neurolink.js +133 -0
- package/dist/lib/proxy/globalInstaller.d.ts +5 -0
- package/dist/lib/proxy/globalInstaller.js +156 -0
- package/dist/lib/proxy/openaiFormat.d.ts +3 -1
- package/dist/lib/proxy/openaiFormat.js +19 -4
- package/dist/lib/proxy/proxyActivity.d.ts +8 -0
- package/dist/lib/proxy/proxyActivity.js +77 -0
- package/dist/lib/server/routes/claudeProxyRoutes.d.ts +56 -6
- package/dist/lib/server/routes/claudeProxyRoutes.js +208 -50
- package/dist/lib/server/routes/openaiProxyRoutes.js +47 -6
- package/dist/lib/types/cli.d.ts +4 -0
- package/dist/lib/types/conversation.d.ts +36 -0
- package/dist/lib/types/conversationMemoryInterface.d.ts +5 -1
- package/dist/lib/types/proxy.d.ts +49 -0
- package/dist/lib/utils/fileDetector.js +34 -10
- package/dist/neurolink.d.ts +27 -0
- package/dist/neurolink.js +133 -0
- package/dist/proxy/globalInstaller.d.ts +5 -0
- package/dist/proxy/globalInstaller.js +155 -0
- package/dist/proxy/openaiFormat.d.ts +3 -1
- package/dist/proxy/openaiFormat.js +19 -4
- package/dist/proxy/proxyActivity.d.ts +8 -0
- package/dist/proxy/proxyActivity.js +76 -0
- package/dist/server/routes/claudeProxyRoutes.d.ts +56 -6
- package/dist/server/routes/claudeProxyRoutes.js +208 -50
- package/dist/server/routes/openaiProxyRoutes.js +47 -6
- package/dist/types/cli.d.ts +4 -0
- package/dist/types/conversation.d.ts +36 -0
- package/dist/types/conversationMemoryInterface.d.ts +5 -1
- package/dist/types/proxy.d.ts +49 -0
- package/dist/utils/fileDetector.js +34 -10
- package/package.json +3 -2
|
@@ -1271,6 +1271,86 @@ User message: "${userMessage}"`;
|
|
|
1271
1271
|
return results;
|
|
1272
1272
|
}
|
|
1273
1273
|
}
|
|
1274
|
+
/**
|
|
1275
|
+
* List all sessions with metadata for CLI/API usage
|
|
1276
|
+
* Implements IConversationMemoryManager.listSessions
|
|
1277
|
+
* @param userId - Optional user identifier to filter sessions
|
|
1278
|
+
* @returns Array of session list items with metadata
|
|
1279
|
+
*/
|
|
1280
|
+
async listSessions(userId) {
|
|
1281
|
+
await this.ensureInitialized();
|
|
1282
|
+
if (!this.redisClient) {
|
|
1283
|
+
return [];
|
|
1284
|
+
}
|
|
1285
|
+
const now = Date.now();
|
|
1286
|
+
const list = [];
|
|
1287
|
+
if (userId) {
|
|
1288
|
+
// List sessions for a specific user
|
|
1289
|
+
const sessionIds = await this.getUserSessions(userId);
|
|
1290
|
+
for (const sessionId of sessionIds) {
|
|
1291
|
+
const session = await this.getUserSessionObject(userId, sessionId);
|
|
1292
|
+
if (!session) {
|
|
1293
|
+
continue;
|
|
1294
|
+
}
|
|
1295
|
+
list.push({
|
|
1296
|
+
id: session.sessionId,
|
|
1297
|
+
title: session.title || session.sessionId,
|
|
1298
|
+
createdAt: session.createdAt,
|
|
1299
|
+
updatedAt: session.updatedAt,
|
|
1300
|
+
userId: session.userId,
|
|
1301
|
+
messageCount: session.messages.length,
|
|
1302
|
+
lastActive: this.formatTimeAgo(now - new Date(session.updatedAt).getTime()),
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
else {
|
|
1307
|
+
// List all sessions across all users by scanning Redis keys
|
|
1308
|
+
const keys = await scanKeys(this.redisClient, `${this.redisConfig.keyPrefix}*`);
|
|
1309
|
+
for (const key of keys) {
|
|
1310
|
+
// Skip user session index keys (they end with :sessions)
|
|
1311
|
+
if (key.endsWith(":sessions")) {
|
|
1312
|
+
continue;
|
|
1313
|
+
}
|
|
1314
|
+
const raw = await this.redisClient.get(key);
|
|
1315
|
+
if (!raw) {
|
|
1316
|
+
continue;
|
|
1317
|
+
}
|
|
1318
|
+
const session = deserializeConversation(raw);
|
|
1319
|
+
if (!session) {
|
|
1320
|
+
continue;
|
|
1321
|
+
}
|
|
1322
|
+
list.push({
|
|
1323
|
+
id: session.sessionId,
|
|
1324
|
+
title: session.title || session.sessionId,
|
|
1325
|
+
createdAt: session.createdAt,
|
|
1326
|
+
updatedAt: session.updatedAt,
|
|
1327
|
+
userId: session.userId,
|
|
1328
|
+
messageCount: session.messages.length,
|
|
1329
|
+
lastActive: this.formatTimeAgo(now - new Date(session.updatedAt).getTime()),
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
return list.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
|
1334
|
+
}
|
|
1335
|
+
/**
|
|
1336
|
+
* Format milliseconds into human-readable time ago string
|
|
1337
|
+
*/
|
|
1338
|
+
formatTimeAgo(ms) {
|
|
1339
|
+
const seconds = Math.floor(ms / 1000);
|
|
1340
|
+
const minutes = Math.floor(seconds / 60);
|
|
1341
|
+
const hours = Math.floor(minutes / 60);
|
|
1342
|
+
const days = Math.floor(hours / 24);
|
|
1343
|
+
if (days > 0) {
|
|
1344
|
+
return `${days} day${days > 1 ? "s" : ""} ago`;
|
|
1345
|
+
}
|
|
1346
|
+
if (hours > 0) {
|
|
1347
|
+
return `${hours} hour${hours > 1 ? "s" : ""} ago`;
|
|
1348
|
+
}
|
|
1349
|
+
if (minutes > 0) {
|
|
1350
|
+
return `${minutes} minute${minutes > 1 ? "s" : ""} ago`;
|
|
1351
|
+
}
|
|
1352
|
+
return "just now";
|
|
1353
|
+
}
|
|
1274
1354
|
/**
|
|
1275
1355
|
* Clean up stale pending tool execution data
|
|
1276
1356
|
* Removes data older than 5 minutes to prevent memory leaks
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Conversation Memory Manager for NeuroLink
|
|
3
3
|
* Handles in-memory conversation storage, session management, and context injection
|
|
4
4
|
*/
|
|
5
|
-
import type { ChatMessage, ConversationMemoryConfig, ConversationMemoryStats, SessionMemory, StoreConversationTurnOptions, IConversationMemoryManager } from "../types/index.js";
|
|
5
|
+
import type { ChatMessage, ConversationMemoryConfig, ConversationMemoryStats, SessionListItem, SessionMemory, StoreConversationTurnOptions, IConversationMemoryManager } from "../types/index.js";
|
|
6
6
|
export declare class ConversationMemoryManager implements IConversationMemoryManager {
|
|
7
7
|
private sessions;
|
|
8
8
|
config: ConversationMemoryConfig;
|
|
@@ -56,6 +56,16 @@ export declare class ConversationMemoryManager implements IConversationMemoryMan
|
|
|
56
56
|
private createNewSession;
|
|
57
57
|
private enforceSessionLimit;
|
|
58
58
|
getStats(): Promise<ConversationMemoryStats>;
|
|
59
|
+
/**
|
|
60
|
+
* List all sessions with metadata
|
|
61
|
+
* @param userId - Optional user ID to filter sessions
|
|
62
|
+
* @returns Array of session list items with metadata
|
|
63
|
+
*/
|
|
64
|
+
listSessions(userId?: string): Promise<SessionListItem[]>;
|
|
65
|
+
/**
|
|
66
|
+
* Format milliseconds into human-readable time ago string
|
|
67
|
+
*/
|
|
68
|
+
private formatTimeAgo;
|
|
59
69
|
clearSession(sessionId: string): Promise<boolean>;
|
|
60
70
|
clearAllSessions(): Promise<void>;
|
|
61
71
|
/**
|
|
@@ -292,6 +292,50 @@ export class ConversationMemoryManager {
|
|
|
292
292
|
totalTurns,
|
|
293
293
|
};
|
|
294
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* List all sessions with metadata
|
|
297
|
+
* @param userId - Optional user ID to filter sessions
|
|
298
|
+
* @returns Array of session list items with metadata
|
|
299
|
+
*/
|
|
300
|
+
async listSessions(userId) {
|
|
301
|
+
await this.ensureInitialized();
|
|
302
|
+
const sessions = Array.from(this.sessions.values());
|
|
303
|
+
const now = Date.now();
|
|
304
|
+
return sessions
|
|
305
|
+
.filter((session) => !userId || session.userId === userId)
|
|
306
|
+
.map((session) => {
|
|
307
|
+
const lastActive = this.formatTimeAgo(now - session.lastActivity);
|
|
308
|
+
return {
|
|
309
|
+
id: session.sessionId,
|
|
310
|
+
title: session.sessionId, // In-memory doesn't store title, use sessionId
|
|
311
|
+
createdAt: new Date(session.createdAt).toISOString(),
|
|
312
|
+
updatedAt: new Date(session.lastActivity).toISOString(),
|
|
313
|
+
userId: session.userId,
|
|
314
|
+
messageCount: session.messages.length,
|
|
315
|
+
lastActive,
|
|
316
|
+
};
|
|
317
|
+
})
|
|
318
|
+
.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Format milliseconds into human-readable time ago string
|
|
322
|
+
*/
|
|
323
|
+
formatTimeAgo(ms) {
|
|
324
|
+
const seconds = Math.floor(ms / 1000);
|
|
325
|
+
const minutes = Math.floor(seconds / 60);
|
|
326
|
+
const hours = Math.floor(minutes / 60);
|
|
327
|
+
const days = Math.floor(hours / 24);
|
|
328
|
+
if (days > 0) {
|
|
329
|
+
return `${days} day${days > 1 ? "s" : ""} ago`;
|
|
330
|
+
}
|
|
331
|
+
if (hours > 0) {
|
|
332
|
+
return `${hours} hour${hours > 1 ? "s" : ""} ago`;
|
|
333
|
+
}
|
|
334
|
+
if (minutes > 0) {
|
|
335
|
+
return `${minutes} minute${minutes > 1 ? "s" : ""} ago`;
|
|
336
|
+
}
|
|
337
|
+
return "just now";
|
|
338
|
+
}
|
|
295
339
|
async clearSession(sessionId) {
|
|
296
340
|
return withSpan({
|
|
297
341
|
name: "neurolink.memory.clear",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Redis Conversation Memory Manager for NeuroLink
|
|
3
3
|
* Redis-based implementation of conversation storage with same interface as ConversationMemoryManager
|
|
4
4
|
*/
|
|
5
|
-
import type { ChatMessage, ConversationMemoryConfig, ConversationMemoryStats, RedisConversationObject, RedisStorageConfig, SessionMemory, SessionMetadata, StoreConversationTurnOptions, AgenticLoopReportMetadata, IConversationMemoryManager } from "../types/index.js";
|
|
5
|
+
import type { ChatMessage, ConversationMemoryConfig, ConversationMemoryStats, RedisConversationObject, RedisStorageConfig, SessionListItem, SessionMemory, SessionMetadata, StoreConversationTurnOptions, AgenticLoopReportMetadata, IConversationMemoryManager } from "../types/index.js";
|
|
6
6
|
/**
|
|
7
7
|
* Redis-based implementation of the ConversationMemoryManager
|
|
8
8
|
* Uses the same interface but stores data in Redis
|
|
@@ -175,6 +175,17 @@ export declare class RedisConversationMemoryManager implements IConversationMemo
|
|
|
175
175
|
* @returns Array of session metadata objects
|
|
176
176
|
*/
|
|
177
177
|
getUserAllSessionsHistory(userId: string): Promise<SessionMetadata[]>;
|
|
178
|
+
/**
|
|
179
|
+
* List all sessions with metadata for CLI/API usage
|
|
180
|
+
* Implements IConversationMemoryManager.listSessions
|
|
181
|
+
* @param userId - Optional user identifier to filter sessions
|
|
182
|
+
* @returns Array of session list items with metadata
|
|
183
|
+
*/
|
|
184
|
+
listSessions(userId?: string): Promise<SessionListItem[]>;
|
|
185
|
+
/**
|
|
186
|
+
* Format milliseconds into human-readable time ago string
|
|
187
|
+
*/
|
|
188
|
+
private formatTimeAgo;
|
|
178
189
|
/**
|
|
179
190
|
* Clean up stale pending tool execution data
|
|
180
191
|
* Removes data older than 5 minutes to prevent memory leaks
|
|
@@ -1271,6 +1271,86 @@ User message: "${userMessage}"`;
|
|
|
1271
1271
|
return results;
|
|
1272
1272
|
}
|
|
1273
1273
|
}
|
|
1274
|
+
/**
|
|
1275
|
+
* List all sessions with metadata for CLI/API usage
|
|
1276
|
+
* Implements IConversationMemoryManager.listSessions
|
|
1277
|
+
* @param userId - Optional user identifier to filter sessions
|
|
1278
|
+
* @returns Array of session list items with metadata
|
|
1279
|
+
*/
|
|
1280
|
+
async listSessions(userId) {
|
|
1281
|
+
await this.ensureInitialized();
|
|
1282
|
+
if (!this.redisClient) {
|
|
1283
|
+
return [];
|
|
1284
|
+
}
|
|
1285
|
+
const now = Date.now();
|
|
1286
|
+
const list = [];
|
|
1287
|
+
if (userId) {
|
|
1288
|
+
// List sessions for a specific user
|
|
1289
|
+
const sessionIds = await this.getUserSessions(userId);
|
|
1290
|
+
for (const sessionId of sessionIds) {
|
|
1291
|
+
const session = await this.getUserSessionObject(userId, sessionId);
|
|
1292
|
+
if (!session) {
|
|
1293
|
+
continue;
|
|
1294
|
+
}
|
|
1295
|
+
list.push({
|
|
1296
|
+
id: session.sessionId,
|
|
1297
|
+
title: session.title || session.sessionId,
|
|
1298
|
+
createdAt: session.createdAt,
|
|
1299
|
+
updatedAt: session.updatedAt,
|
|
1300
|
+
userId: session.userId,
|
|
1301
|
+
messageCount: session.messages.length,
|
|
1302
|
+
lastActive: this.formatTimeAgo(now - new Date(session.updatedAt).getTime()),
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
else {
|
|
1307
|
+
// List all sessions across all users by scanning Redis keys
|
|
1308
|
+
const keys = await scanKeys(this.redisClient, `${this.redisConfig.keyPrefix}*`);
|
|
1309
|
+
for (const key of keys) {
|
|
1310
|
+
// Skip user session index keys (they end with :sessions)
|
|
1311
|
+
if (key.endsWith(":sessions")) {
|
|
1312
|
+
continue;
|
|
1313
|
+
}
|
|
1314
|
+
const raw = await this.redisClient.get(key);
|
|
1315
|
+
if (!raw) {
|
|
1316
|
+
continue;
|
|
1317
|
+
}
|
|
1318
|
+
const session = deserializeConversation(raw);
|
|
1319
|
+
if (!session) {
|
|
1320
|
+
continue;
|
|
1321
|
+
}
|
|
1322
|
+
list.push({
|
|
1323
|
+
id: session.sessionId,
|
|
1324
|
+
title: session.title || session.sessionId,
|
|
1325
|
+
createdAt: session.createdAt,
|
|
1326
|
+
updatedAt: session.updatedAt,
|
|
1327
|
+
userId: session.userId,
|
|
1328
|
+
messageCount: session.messages.length,
|
|
1329
|
+
lastActive: this.formatTimeAgo(now - new Date(session.updatedAt).getTime()),
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
return list.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
|
1334
|
+
}
|
|
1335
|
+
/**
|
|
1336
|
+
* Format milliseconds into human-readable time ago string
|
|
1337
|
+
*/
|
|
1338
|
+
formatTimeAgo(ms) {
|
|
1339
|
+
const seconds = Math.floor(ms / 1000);
|
|
1340
|
+
const minutes = Math.floor(seconds / 60);
|
|
1341
|
+
const hours = Math.floor(minutes / 60);
|
|
1342
|
+
const days = Math.floor(hours / 24);
|
|
1343
|
+
if (days > 0) {
|
|
1344
|
+
return `${days} day${days > 1 ? "s" : ""} ago`;
|
|
1345
|
+
}
|
|
1346
|
+
if (hours > 0) {
|
|
1347
|
+
return `${hours} hour${hours > 1 ? "s" : ""} ago`;
|
|
1348
|
+
}
|
|
1349
|
+
if (minutes > 0) {
|
|
1350
|
+
return `${minutes} minute${minutes > 1 ? "s" : ""} ago`;
|
|
1351
|
+
}
|
|
1352
|
+
return "just now";
|
|
1353
|
+
}
|
|
1274
1354
|
/**
|
|
1275
1355
|
* Clean up stale pending tool execution data
|
|
1276
1356
|
* Removes data older than 5 minutes to prevent memory leaks
|
package/dist/lib/neurolink.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { MCPToolRegistry } from "./mcp/toolRegistry.js";
|
|
|
14
14
|
import type { DynamicOptions } from "./types/index.js";
|
|
15
15
|
import { SkillsManager } from "./skills/skillsManager.js";
|
|
16
16
|
import { TaskManager } from "./tasks/taskManager.js";
|
|
17
|
+
import type { SessionExport, SessionListItem } from "./types/index.js";
|
|
17
18
|
/**
|
|
18
19
|
* Curator P2-4 dedup (concurrency-safe): native providers emit
|
|
19
20
|
* `generation:end` on the shared SDK emitter. We attach a fresh
|
|
@@ -1631,6 +1632,32 @@ export declare class NeuroLink {
|
|
|
1631
1632
|
* Clear all conversation history (public API)
|
|
1632
1633
|
*/
|
|
1633
1634
|
clearAllConversations(): Promise<void>;
|
|
1635
|
+
/**
|
|
1636
|
+
* List all conversation sessions with metadata (public API)
|
|
1637
|
+
* @param userId - Optional user ID to filter sessions (required for Redis storage)
|
|
1638
|
+
* @returns Array of session list items with metadata
|
|
1639
|
+
*/
|
|
1640
|
+
listSessions(userId?: string): Promise<SessionListItem[]>;
|
|
1641
|
+
/**
|
|
1642
|
+
* Export a single session with full history and metadata (public API)
|
|
1643
|
+
* @param sessionId - The session ID to export
|
|
1644
|
+
* @param options - Export options
|
|
1645
|
+
* @returns Session export object with full history
|
|
1646
|
+
*/
|
|
1647
|
+
exportSession(sessionId: string, options?: {
|
|
1648
|
+
includeMetadata?: boolean;
|
|
1649
|
+
format?: "json" | "csv";
|
|
1650
|
+
}): Promise<SessionExport | null>;
|
|
1651
|
+
/**
|
|
1652
|
+
* Export all sessions for a user (public API)
|
|
1653
|
+
* @param userId - Optional user ID (required for Redis storage)
|
|
1654
|
+
* @param options - Export options
|
|
1655
|
+
* @returns Array of session exports
|
|
1656
|
+
*/
|
|
1657
|
+
exportAllSessions(userId?: string, options?: {
|
|
1658
|
+
includeMetadata?: boolean;
|
|
1659
|
+
format?: "json" | "csv";
|
|
1660
|
+
}): Promise<SessionExport[]>;
|
|
1634
1661
|
/**
|
|
1635
1662
|
* Store tool executions in conversation memory if enabled and Redis is configured
|
|
1636
1663
|
* @param sessionId - Session identifier
|
package/dist/lib/neurolink.js
CHANGED
|
@@ -10504,6 +10504,139 @@ Current user's request: ${currentInput}`;
|
|
|
10504
10504
|
this.lastCompactionMessageCount.clear();
|
|
10505
10505
|
await this.conversationMemory.clearAllSessions();
|
|
10506
10506
|
}
|
|
10507
|
+
/**
|
|
10508
|
+
* List all conversation sessions with metadata (public API)
|
|
10509
|
+
* @param userId - Optional user ID to filter sessions (required for Redis storage)
|
|
10510
|
+
* @returns Array of session list items with metadata
|
|
10511
|
+
*/
|
|
10512
|
+
async listSessions(userId) {
|
|
10513
|
+
// First ensure memory is initialized
|
|
10514
|
+
const initId = `list-sessions-init-${Date.now()}`;
|
|
10515
|
+
await this.initializeConversationMemoryForGeneration(initId, Date.now(), process.hrtime.bigint());
|
|
10516
|
+
if (!this.conversationMemory) {
|
|
10517
|
+
throw new Error("Conversation memory is not enabled");
|
|
10518
|
+
}
|
|
10519
|
+
// Check if listSessions is available on the memory manager
|
|
10520
|
+
if (!this.conversationMemory.listSessions) {
|
|
10521
|
+
logger.warn("listSessions not available on current memory manager");
|
|
10522
|
+
return [];
|
|
10523
|
+
}
|
|
10524
|
+
const MEMORY_OPERATION_TIMEOUT = 30000; // 30 seconds
|
|
10525
|
+
try {
|
|
10526
|
+
const sessions = await withTimeout(this.conversationMemory.listSessions(userId), MEMORY_OPERATION_TIMEOUT, new Error("listSessions operation timed out after 30s"));
|
|
10527
|
+
logger.debug("Listed conversation sessions", {
|
|
10528
|
+
userId,
|
|
10529
|
+
sessionCount: sessions.length,
|
|
10530
|
+
});
|
|
10531
|
+
return sessions;
|
|
10532
|
+
}
|
|
10533
|
+
catch (error) {
|
|
10534
|
+
logger.error("Failed to list conversation sessions", {
|
|
10535
|
+
userId,
|
|
10536
|
+
error: error instanceof Error ? error.message : String(error),
|
|
10537
|
+
});
|
|
10538
|
+
return [];
|
|
10539
|
+
}
|
|
10540
|
+
}
|
|
10541
|
+
/**
|
|
10542
|
+
* Export a single session with full history and metadata (public API)
|
|
10543
|
+
* @param sessionId - The session ID to export
|
|
10544
|
+
* @param options - Export options
|
|
10545
|
+
* @returns Session export object with full history
|
|
10546
|
+
*/
|
|
10547
|
+
async exportSession(sessionId, options = {}) {
|
|
10548
|
+
// First ensure memory is initialized
|
|
10549
|
+
const initId = `export-session-init-${Date.now()}`;
|
|
10550
|
+
await this.initializeConversationMemoryForGeneration(initId, Date.now(), process.hrtime.bigint());
|
|
10551
|
+
if (!this.conversationMemory) {
|
|
10552
|
+
throw new Error("Conversation memory is not enabled");
|
|
10553
|
+
}
|
|
10554
|
+
if (!sessionId || typeof sessionId !== "string") {
|
|
10555
|
+
throw new Error("Session ID must be a non-empty string");
|
|
10556
|
+
}
|
|
10557
|
+
const MEMORY_OPERATION_TIMEOUT = 30000; // 30 seconds
|
|
10558
|
+
try {
|
|
10559
|
+
const messages = await withTimeout(this.conversationMemory.buildContextMessages(sessionId), MEMORY_OPERATION_TIMEOUT, new Error("buildContextMessages operation timed out after 30s"));
|
|
10560
|
+
if (messages.length === 0) {
|
|
10561
|
+
logger.debug("No messages found for session export", { sessionId });
|
|
10562
|
+
return null;
|
|
10563
|
+
}
|
|
10564
|
+
const sessionResult = this.conversationMemory.getSession(sessionId);
|
|
10565
|
+
const session = await withTimeout(sessionResult instanceof Promise
|
|
10566
|
+
? sessionResult
|
|
10567
|
+
: Promise.resolve(sessionResult), MEMORY_OPERATION_TIMEOUT, new Error("getSession operation timed out after 30s"));
|
|
10568
|
+
const now = new Date().toISOString();
|
|
10569
|
+
const exportData = {
|
|
10570
|
+
sessionId,
|
|
10571
|
+
title: sessionId, // Use sessionId as title if not available
|
|
10572
|
+
userId: session?.userId,
|
|
10573
|
+
createdAt: session?.createdAt
|
|
10574
|
+
? new Date(session.createdAt).toISOString()
|
|
10575
|
+
: now,
|
|
10576
|
+
updatedAt: session?.lastActivity
|
|
10577
|
+
? new Date(session.lastActivity).toISOString()
|
|
10578
|
+
: now,
|
|
10579
|
+
messages,
|
|
10580
|
+
};
|
|
10581
|
+
if (options.includeMetadata) {
|
|
10582
|
+
exportData.exportMetadata = {
|
|
10583
|
+
exportedAt: now,
|
|
10584
|
+
exportFormat: options.format || "json",
|
|
10585
|
+
};
|
|
10586
|
+
}
|
|
10587
|
+
logger.debug("Exported conversation session", {
|
|
10588
|
+
sessionId,
|
|
10589
|
+
messageCount: messages.length,
|
|
10590
|
+
});
|
|
10591
|
+
return exportData;
|
|
10592
|
+
}
|
|
10593
|
+
catch (error) {
|
|
10594
|
+
logger.error("Failed to export conversation session", {
|
|
10595
|
+
sessionId,
|
|
10596
|
+
error: error instanceof Error ? error.message : String(error),
|
|
10597
|
+
});
|
|
10598
|
+
return null;
|
|
10599
|
+
}
|
|
10600
|
+
}
|
|
10601
|
+
/**
|
|
10602
|
+
* Export all sessions for a user (public API)
|
|
10603
|
+
* @param userId - Optional user ID (required for Redis storage)
|
|
10604
|
+
* @param options - Export options
|
|
10605
|
+
* @returns Array of session exports
|
|
10606
|
+
*/
|
|
10607
|
+
async exportAllSessions(userId, options = {}) {
|
|
10608
|
+
// First ensure memory is initialized
|
|
10609
|
+
const initId = `export-all-init-${Date.now()}`;
|
|
10610
|
+
await this.initializeConversationMemoryForGeneration(initId, Date.now(), process.hrtime.bigint());
|
|
10611
|
+
if (!this.conversationMemory) {
|
|
10612
|
+
throw new Error("Conversation memory is not enabled");
|
|
10613
|
+
}
|
|
10614
|
+
const MEMORY_OPERATION_TIMEOUT = 30000; // 30 seconds
|
|
10615
|
+
const EXPORT_SESSION_TIMEOUT = 60000; // 60 seconds for full export
|
|
10616
|
+
try {
|
|
10617
|
+
// Get all session IDs
|
|
10618
|
+
const sessions = await withTimeout(this.listSessions(userId), MEMORY_OPERATION_TIMEOUT, new Error("listSessions operation timed out after 30s"));
|
|
10619
|
+
const exports = [];
|
|
10620
|
+
for (const session of sessions) {
|
|
10621
|
+
const exportData = await withTimeout(this.exportSession(session.id, options), EXPORT_SESSION_TIMEOUT, new Error(`exportSession operation timed out after 60s for session ${session.id}`));
|
|
10622
|
+
if (exportData) {
|
|
10623
|
+
exports.push(exportData);
|
|
10624
|
+
}
|
|
10625
|
+
}
|
|
10626
|
+
logger.debug("Exported all conversation sessions", {
|
|
10627
|
+
userId,
|
|
10628
|
+
sessionCount: exports.length,
|
|
10629
|
+
});
|
|
10630
|
+
return exports;
|
|
10631
|
+
}
|
|
10632
|
+
catch (error) {
|
|
10633
|
+
logger.error("Failed to export all conversation sessions", {
|
|
10634
|
+
userId,
|
|
10635
|
+
error: error instanceof Error ? error.message : String(error),
|
|
10636
|
+
});
|
|
10637
|
+
return [];
|
|
10638
|
+
}
|
|
10639
|
+
}
|
|
10507
10640
|
/**
|
|
10508
10641
|
* Store tool executions in conversation memory if enabled and Redis is configured
|
|
10509
10642
|
* @param sessionId - Session identifier
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { GlobalInstallerKind, GlobalInstallerResolution, ResolveGlobalInstallerOptions } from "../types/index.js";
|
|
2
|
+
/** Resolve a package manager that can update the installation currently running. */
|
|
3
|
+
export declare function resolveGlobalInstaller(options?: ResolveGlobalInstallerOptions): GlobalInstallerResolution;
|
|
4
|
+
export declare function getGlobalInstallArgs(kind: GlobalInstallerKind, packageSpec: string): string[];
|
|
5
|
+
export declare function describeInstallFailure(error: unknown): string;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { execFileSync as nodeExecFileSync } from "node:child_process";
|
|
2
|
+
import { accessSync, constants, existsSync, realpathSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
5
|
+
function runText(execFileSync, bin, args) {
|
|
6
|
+
return String(execFileSync(bin, args, {
|
|
7
|
+
encoding: "utf8",
|
|
8
|
+
timeout: 10_000,
|
|
9
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
10
|
+
})).trim();
|
|
11
|
+
}
|
|
12
|
+
function writableDirectory(path) {
|
|
13
|
+
try {
|
|
14
|
+
if (!existsSync(path)) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
accessSync(path, constants.W_OK);
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function isPathInside(path, parent) {
|
|
25
|
+
if (!path) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const candidate = realpathSync(path);
|
|
30
|
+
const root = realpathSync(parent);
|
|
31
|
+
return candidate === root || candidate.startsWith(`${root}/`);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
const candidate = resolve(path);
|
|
35
|
+
const root = resolve(parent);
|
|
36
|
+
return candidate === root || candidate.startsWith(`${root}/`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function probeInstaller(kind, bin, entryScript, execFileSync) {
|
|
40
|
+
const base = {
|
|
41
|
+
kind,
|
|
42
|
+
bin,
|
|
43
|
+
working: false,
|
|
44
|
+
installable: false,
|
|
45
|
+
matchesCurrentInstall: false,
|
|
46
|
+
};
|
|
47
|
+
try {
|
|
48
|
+
base.version = runText(execFileSync, bin, ["--version"]);
|
|
49
|
+
base.working = base.version.length > 0;
|
|
50
|
+
base.globalRoot = runText(execFileSync, bin, ["root", "-g"]);
|
|
51
|
+
base.globalBinDir =
|
|
52
|
+
kind === "pnpm"
|
|
53
|
+
? runText(execFileSync, bin, ["bin", "-g"])
|
|
54
|
+
: join(runText(execFileSync, bin, ["prefix", "-g"]), "bin");
|
|
55
|
+
if (!base.globalRoot || !writableDirectory(base.globalRoot)) {
|
|
56
|
+
base.reason = "global package root is missing or not writable";
|
|
57
|
+
return base;
|
|
58
|
+
}
|
|
59
|
+
if (!base.globalBinDir || !writableDirectory(base.globalBinDir)) {
|
|
60
|
+
base.reason = "global executable directory is missing or not writable";
|
|
61
|
+
return base;
|
|
62
|
+
}
|
|
63
|
+
base.installable = true;
|
|
64
|
+
base.matchesCurrentInstall = isPathInside(entryScript, base.globalRoot);
|
|
65
|
+
return base;
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
base.reason = error instanceof Error ? error.message : String(error);
|
|
69
|
+
return base;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function resolveFromPath(command, execFileSync) {
|
|
73
|
+
try {
|
|
74
|
+
const result = runText(execFileSync, "which", [command]);
|
|
75
|
+
return result || undefined;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** Resolve a package manager that can update the installation currently running. */
|
|
82
|
+
export function resolveGlobalInstaller(options = {}) {
|
|
83
|
+
const env = options.env ?? process.env;
|
|
84
|
+
const homeDir = options.homeDir ?? homedir();
|
|
85
|
+
const entryScript = options.entryScript ?? process.argv[1];
|
|
86
|
+
const execFileSync = options.execFileSync ?? nodeExecFileSync;
|
|
87
|
+
const candidates = [];
|
|
88
|
+
if (env.NEUROLINK_PACKAGE_MANAGER_PATH) {
|
|
89
|
+
const configuredKind = env.NEUROLINK_PACKAGE_MANAGER?.toLowerCase();
|
|
90
|
+
const inferredName = basename(env.NEUROLINK_PACKAGE_MANAGER_PATH);
|
|
91
|
+
const kind = configuredKind === "npm" || configuredKind === "pnpm"
|
|
92
|
+
? configuredKind
|
|
93
|
+
: inferredName.startsWith("npm")
|
|
94
|
+
? "npm"
|
|
95
|
+
: "pnpm";
|
|
96
|
+
candidates.push({ kind, bin: env.NEUROLINK_PACKAGE_MANAGER_PATH });
|
|
97
|
+
}
|
|
98
|
+
if (env.NEUROLINK_PNPM_PATH) {
|
|
99
|
+
candidates.push({ kind: "pnpm", bin: env.NEUROLINK_PNPM_PATH });
|
|
100
|
+
}
|
|
101
|
+
if (env.PNPM_HOME) {
|
|
102
|
+
candidates.push({ kind: "pnpm", bin: join(env.PNPM_HOME, "pnpm") });
|
|
103
|
+
}
|
|
104
|
+
const nodeBinDir = dirname(process.execPath);
|
|
105
|
+
candidates.push({ kind: "npm", bin: join(nodeBinDir, "npm") });
|
|
106
|
+
const pathPnpm = resolveFromPath("pnpm", execFileSync);
|
|
107
|
+
const pathNpm = resolveFromPath("npm", execFileSync);
|
|
108
|
+
if (pathPnpm) {
|
|
109
|
+
candidates.push({ kind: "pnpm", bin: pathPnpm });
|
|
110
|
+
}
|
|
111
|
+
if (pathNpm) {
|
|
112
|
+
candidates.push({ kind: "npm", bin: pathNpm });
|
|
113
|
+
}
|
|
114
|
+
candidates.push({ kind: "pnpm", bin: join(homeDir, ".local", "share", "pnpm", "pnpm") }, { kind: "pnpm", bin: join(homeDir, "Library", "pnpm", "pnpm") }, { kind: "npm", bin: "/opt/homebrew/bin/npm" }, { kind: "npm", bin: "/usr/local/bin/npm" });
|
|
115
|
+
const seen = new Set();
|
|
116
|
+
const tried = candidates
|
|
117
|
+
.filter(({ kind, bin }) => {
|
|
118
|
+
const key = `${kind}:${bin}`;
|
|
119
|
+
if (!bin || seen.has(key)) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
seen.add(key);
|
|
123
|
+
return true;
|
|
124
|
+
})
|
|
125
|
+
.map(({ kind, bin }) => probeInstaller(kind, bin, entryScript, execFileSync));
|
|
126
|
+
const matchingInstaller = tried.find((probe) => probe.installable && probe.matchesCurrentInstall);
|
|
127
|
+
// When the running entry script is known, installing into a different
|
|
128
|
+
// global root cannot update that process and may shadow another install.
|
|
129
|
+
const installer = entryScript
|
|
130
|
+
? matchingInstaller
|
|
131
|
+
: (matchingInstaller ?? tried.find((probe) => probe.installable));
|
|
132
|
+
return { installer, tried };
|
|
133
|
+
}
|
|
134
|
+
export function getGlobalInstallArgs(kind, packageSpec) {
|
|
135
|
+
return kind === "pnpm"
|
|
136
|
+
? ["add", "-g", packageSpec]
|
|
137
|
+
: ["install", "--global", "--no-audit", "--no-fund", packageSpec];
|
|
138
|
+
}
|
|
139
|
+
function capturedOutput(error, key) {
|
|
140
|
+
if (!error || typeof error !== "object" || !(key in error)) {
|
|
141
|
+
return "";
|
|
142
|
+
}
|
|
143
|
+
const raw = error[key];
|
|
144
|
+
return String(raw ?? "")
|
|
145
|
+
.trim()
|
|
146
|
+
.slice(0, 1_000);
|
|
147
|
+
}
|
|
148
|
+
export function describeInstallFailure(error) {
|
|
149
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
150
|
+
const stdout = capturedOutput(error, "stdout");
|
|
151
|
+
const stderr = capturedOutput(error, "stderr");
|
|
152
|
+
return [message, stdout && `stdout: ${stdout}`, stderr && `stderr: ${stderr}`]
|
|
153
|
+
.filter(Boolean)
|
|
154
|
+
.join("\n");
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=globalInstaller.js.map
|
|
@@ -134,4 +134,6 @@ export declare function convertClaudeToOpenAIResponse(claude: ClaudeResponse, re
|
|
|
134
134
|
* - message_delta -> captures stop_reason and output token usage
|
|
135
135
|
* - message_stop -> emits the final `finish_reason` chunk + `[DONE]`
|
|
136
136
|
*/
|
|
137
|
-
export declare function createClaudeToOpenAIStreamTransform(requestModel: string
|
|
137
|
+
export declare function createClaudeToOpenAIStreamTransform(requestModel: string, options?: {
|
|
138
|
+
onError?: (message: string) => void;
|
|
139
|
+
}): TransformStream<Uint8Array, Uint8Array>;
|