agent-consultation-mcp 1.0.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.
Files changed (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +140 -0
  3. package/dist/api/index.d.ts +27 -0
  4. package/dist/api/index.js +213 -0
  5. package/dist/api/index.js.map +1 -0
  6. package/dist/api/middleware/security.d.ts +6 -0
  7. package/dist/api/middleware/security.js +28 -0
  8. package/dist/api/middleware/security.js.map +1 -0
  9. package/dist/api/routes/chat.d.ts +2 -0
  10. package/dist/api/routes/chat.js +61 -0
  11. package/dist/api/routes/chat.js.map +1 -0
  12. package/dist/api/routes/config.d.ts +2 -0
  13. package/dist/api/routes/config.js +81 -0
  14. package/dist/api/routes/config.js.map +1 -0
  15. package/dist/api/routes/providers.d.ts +2 -0
  16. package/dist/api/routes/providers.js +225 -0
  17. package/dist/api/routes/providers.js.map +1 -0
  18. package/dist/api/standalone-server.d.ts +12 -0
  19. package/dist/api/standalone-server.js +91 -0
  20. package/dist/api/standalone-server.js.map +1 -0
  21. package/dist/config/defaults.d.ts +17 -0
  22. package/dist/config/defaults.js +30 -0
  23. package/dist/config/defaults.js.map +1 -0
  24. package/dist/config/encryption.d.ts +12 -0
  25. package/dist/config/encryption.js +85 -0
  26. package/dist/config/encryption.js.map +1 -0
  27. package/dist/config/index.d.ts +5 -0
  28. package/dist/config/index.js +6 -0
  29. package/dist/config/index.js.map +1 -0
  30. package/dist/config/manager.d.ts +62 -0
  31. package/dist/config/manager.js +186 -0
  32. package/dist/config/manager.js.map +1 -0
  33. package/dist/config/prompts.d.ts +10 -0
  34. package/dist/config/prompts.js +140 -0
  35. package/dist/config/prompts.js.map +1 -0
  36. package/dist/config/schema.d.ts +141 -0
  37. package/dist/config/schema.js +54 -0
  38. package/dist/config/schema.js.map +1 -0
  39. package/dist/index.d.ts +2 -0
  40. package/dist/index.js +224 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/providers/base.d.ts +44 -0
  43. package/dist/providers/base.js +84 -0
  44. package/dist/providers/base.js.map +1 -0
  45. package/dist/providers/deepseek.d.ts +30 -0
  46. package/dist/providers/deepseek.js +148 -0
  47. package/dist/providers/deepseek.js.map +1 -0
  48. package/dist/providers/index.d.ts +5 -0
  49. package/dist/providers/index.js +8 -0
  50. package/dist/providers/index.js.map +1 -0
  51. package/dist/providers/openai.d.ts +30 -0
  52. package/dist/providers/openai.js +123 -0
  53. package/dist/providers/openai.js.map +1 -0
  54. package/dist/providers/registry.d.ts +37 -0
  55. package/dist/providers/registry.js +65 -0
  56. package/dist/providers/registry.js.map +1 -0
  57. package/dist/providers/types.d.ts +71 -0
  58. package/dist/providers/types.js +2 -0
  59. package/dist/providers/types.js.map +1 -0
  60. package/dist/server/conversation.d.ts +101 -0
  61. package/dist/server/conversation.js +275 -0
  62. package/dist/server/conversation.js.map +1 -0
  63. package/dist/server/index.d.ts +2 -0
  64. package/dist/server/index.js +3 -0
  65. package/dist/server/index.js.map +1 -0
  66. package/dist/server/tools/consult.d.ts +15 -0
  67. package/dist/server/tools/consult.js +164 -0
  68. package/dist/server/tools/consult.js.map +1 -0
  69. package/dist/server/tools/index.d.ts +1 -0
  70. package/dist/server/tools/index.js +2 -0
  71. package/dist/server/tools/index.js.map +1 -0
  72. package/dist/types/config.d.ts +20 -0
  73. package/dist/types/config.js +2 -0
  74. package/dist/types/config.js.map +1 -0
  75. package/dist/types/index.d.ts +3 -0
  76. package/dist/types/index.js +4 -0
  77. package/dist/types/index.js.map +1 -0
  78. package/dist/types/messages.d.ts +48 -0
  79. package/dist/types/messages.js +2 -0
  80. package/dist/types/messages.js.map +1 -0
  81. package/dist/types/models.d.ts +39 -0
  82. package/dist/types/models.js +66 -0
  83. package/dist/types/models.js.map +1 -0
  84. package/dist/ui/index.html +1044 -0
  85. package/dist/utils/errors.d.ts +32 -0
  86. package/dist/utils/errors.js +53 -0
  87. package/dist/utils/errors.js.map +1 -0
  88. package/dist/utils/index.d.ts +2 -0
  89. package/dist/utils/index.js +3 -0
  90. package/dist/utils/index.js.map +1 -0
  91. package/dist/utils/logger.d.ts +13 -0
  92. package/dist/utils/logger.js +73 -0
  93. package/dist/utils/logger.js.map +1 -0
  94. package/package.json +65 -0
@@ -0,0 +1,123 @@
1
+ import OpenAI from 'openai';
2
+ import { MODEL_CONFIG } from '../types/index.js';
3
+ import { BaseProvider } from './base.js';
4
+ import { logger } from '../utils/index.js';
5
+ /**
6
+ * OpenAI Provider - GPT-5.2 models
7
+ */
8
+ export class OpenAIProvider extends BaseProvider {
9
+ name = 'openai';
10
+ client = null;
11
+ /**
12
+ * Get or create the OpenAI client
13
+ */
14
+ getClient() {
15
+ if (!this.client) {
16
+ const apiKey = this.getApiKey();
17
+ if (!apiKey) {
18
+ throw this.createError('AUTH_ERROR', 'OpenAI API key not configured', false);
19
+ }
20
+ this.client = new OpenAI({
21
+ apiKey,
22
+ });
23
+ }
24
+ return this.client;
25
+ }
26
+ /**
27
+ * Check if a model is a reasoning model
28
+ */
29
+ isReasoningModel(model) {
30
+ const config = MODEL_CONFIG[model];
31
+ return config?.isReasoning ?? false;
32
+ }
33
+ /**
34
+ * Get reasoning effort for the model
35
+ */
36
+ getReasoningEffort(model) {
37
+ const config = MODEL_CONFIG[model];
38
+ return config?.reasoningEffort;
39
+ }
40
+ /**
41
+ * Get the actual API model ID
42
+ */
43
+ getApiModel(model) {
44
+ const config = MODEL_CONFIG[model];
45
+ return config?.apiModel ?? model;
46
+ }
47
+ /**
48
+ * Create a chat completion
49
+ */
50
+ async createCompletion(messages, options) {
51
+ const client = this.getClient();
52
+ const apiModel = this.getApiModel(options.model);
53
+ const isReasoning = this.isReasoningModel(options.model);
54
+ const reasoningEffort = this.getReasoningEffort(options.model);
55
+ this.logRequest(apiModel, messages.length, options);
56
+ const startTime = Date.now();
57
+ try {
58
+ // Build messages array
59
+ const chatMessages = [];
60
+ // Add system prompt if provided
61
+ if (options.systemPrompt) {
62
+ chatMessages.push({
63
+ role: 'system',
64
+ content: options.systemPrompt,
65
+ });
66
+ }
67
+ // Add conversation messages
68
+ chatMessages.push(...messages.map((m) => ({
69
+ role: m.role,
70
+ content: m.content,
71
+ })));
72
+ // Build request parameters
73
+ const requestParams = {
74
+ model: apiModel,
75
+ messages: chatMessages,
76
+ max_tokens: options.maxTokens || 4096,
77
+ };
78
+ // Add reasoning effort for GPT-5.2 models
79
+ if (isReasoning && reasoningEffort) {
80
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
81
+ requestParams.reasoning_effort = reasoningEffort;
82
+ }
83
+ // Set temperature for non-reasoning or if specified
84
+ if (options.temperature !== undefined) {
85
+ requestParams.temperature = options.temperature;
86
+ }
87
+ const response = await client.chat.completions.create(requestParams);
88
+ const choice = response.choices[0];
89
+ const content = choice?.message?.content || '';
90
+ const responseTime = Date.now() - startTime;
91
+ logger.debug('OpenAI response received', {
92
+ model: apiModel,
93
+ responseTime,
94
+ finishReason: choice?.finish_reason,
95
+ });
96
+ const result = {
97
+ content,
98
+ model: response.model,
99
+ usage: response.usage
100
+ ? {
101
+ promptTokens: response.usage.prompt_tokens,
102
+ completionTokens: response.usage.completion_tokens,
103
+ totalTokens: response.usage.total_tokens,
104
+ }
105
+ : undefined,
106
+ finishReason: choice?.finish_reason || undefined,
107
+ responseTime,
108
+ };
109
+ this.logResponse(result);
110
+ return result;
111
+ }
112
+ catch (error) {
113
+ if (error instanceof OpenAI.APIError) {
114
+ const retryable = error.status === 429 ||
115
+ error.status === 500 ||
116
+ error.status === 503;
117
+ throw this.createError(error.code || 'API_ERROR', error.message, retryable, error.status);
118
+ }
119
+ throw error;
120
+ }
121
+ }
122
+ }
123
+ //# sourceMappingURL=openai.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/providers/openai.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import type { ProviderType } from '../types/index.js';
2
+ import type { IProvider } from './types.js';
3
+ /**
4
+ * Registry for managing provider instances
5
+ */
6
+ export declare class ProviderRegistry {
7
+ private providers;
8
+ /**
9
+ * Register a provider
10
+ */
11
+ register(provider: IProvider): void;
12
+ /**
13
+ * Get a provider by name
14
+ */
15
+ getProvider(name: ProviderType): IProvider;
16
+ /**
17
+ * Check if a provider exists
18
+ */
19
+ hasProvider(name: ProviderType): boolean;
20
+ /**
21
+ * Get all registered providers
22
+ */
23
+ getAllProviders(): IProvider[];
24
+ /**
25
+ * Get all configured providers
26
+ */
27
+ getConfiguredProviders(): IProvider[];
28
+ }
29
+ /**
30
+ * Get the provider registry instance
31
+ */
32
+ export declare function getProviderRegistry(): ProviderRegistry;
33
+ /**
34
+ * Initialize the registry with all providers
35
+ * Called during server startup
36
+ */
37
+ export declare function initializeProviders(): Promise<void>;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Registry for managing provider instances
3
+ */
4
+ export class ProviderRegistry {
5
+ providers = new Map();
6
+ /**
7
+ * Register a provider
8
+ */
9
+ register(provider) {
10
+ this.providers.set(provider.name, provider);
11
+ }
12
+ /**
13
+ * Get a provider by name
14
+ */
15
+ getProvider(name) {
16
+ const provider = this.providers.get(name);
17
+ if (!provider) {
18
+ throw new Error(`Provider not found: ${name}`);
19
+ }
20
+ return provider;
21
+ }
22
+ /**
23
+ * Check if a provider exists
24
+ */
25
+ hasProvider(name) {
26
+ return this.providers.has(name);
27
+ }
28
+ /**
29
+ * Get all registered providers
30
+ */
31
+ getAllProviders() {
32
+ return Array.from(this.providers.values());
33
+ }
34
+ /**
35
+ * Get all configured providers
36
+ */
37
+ getConfiguredProviders() {
38
+ return this.getAllProviders().filter((p) => p.isConfigured());
39
+ }
40
+ }
41
+ // Singleton instance
42
+ let registryInstance = null;
43
+ /**
44
+ * Get the provider registry instance
45
+ */
46
+ export function getProviderRegistry() {
47
+ if (!registryInstance) {
48
+ registryInstance = new ProviderRegistry();
49
+ }
50
+ return registryInstance;
51
+ }
52
+ /**
53
+ * Initialize the registry with all providers
54
+ * Called during server startup
55
+ */
56
+ export async function initializeProviders() {
57
+ const registry = getProviderRegistry();
58
+ // Import providers dynamically to avoid circular dependencies
59
+ const { DeepSeekProvider } = await import('./deepseek.js');
60
+ const { OpenAIProvider } = await import('./openai.js');
61
+ // Register providers
62
+ registry.register(new DeepSeekProvider());
63
+ registry.register(new OpenAIProvider());
64
+ }
65
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/providers/registry.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,OAAO,gBAAgB;IACnB,SAAS,GAAiC,IAAI,GAAG,EAAE,CAAC;IAE5D;;OAEG;IACH,QAAQ,CAAC,QAAmB;QAC1B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC9C,CAAC;IAED;;OAEG;IACH,WAAW,CAAC,IAAkB;QAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,EAAE,CAAC,CAAC;QACjD,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED;;OAEG;IACH,WAAW,CAAC,IAAkB;QAC5B,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IAED;;OAEG;IACH,eAAe;QACb,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED;;OAEG;IACH,sBAAsB;QACpB,OAAO,IAAI,CAAC,eAAe,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC;IAChE,CAAC;CACF;AAED,qBAAqB;AACrB,IAAI,gBAAgB,GAA4B,IAAI,CAAC;AAErD;;GAEG;AACH,MAAM,UAAU,mBAAmB;IACjC,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACtB,gBAAgB,GAAG,IAAI,gBAAgB,EAAE,CAAC;IAC5C,CAAC;IACD,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB;IACvC,MAAM,QAAQ,GAAG,mBAAmB,EAAE,CAAC;IAEvC,8DAA8D;IAC9D,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;IAC3D,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;IAEvD,qBAAqB;IACrB,QAAQ,CAAC,QAAQ,CAAC,IAAI,gBAAgB,EAAE,CAAC,CAAC;IAC1C,QAAQ,CAAC,QAAQ,CAAC,IAAI,cAAc,EAAE,CAAC,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,71 @@
1
+ import type { ModelType, ProviderType } from '../types/index.js';
2
+ /**
3
+ * Message format for provider communication
4
+ */
5
+ export interface ProviderMessage {
6
+ role: 'user' | 'assistant' | 'system';
7
+ content: string;
8
+ }
9
+ /**
10
+ * Options for creating a completion
11
+ */
12
+ export interface CompletionOptions {
13
+ model: ModelType;
14
+ systemPrompt?: string;
15
+ maxTokens?: number;
16
+ temperature?: number;
17
+ }
18
+ /**
19
+ * Token usage information
20
+ */
21
+ export interface TokenUsage {
22
+ promptTokens: number;
23
+ completionTokens: number;
24
+ totalTokens: number;
25
+ }
26
+ /**
27
+ * Response from a provider
28
+ */
29
+ export interface ProviderResponse {
30
+ content: string;
31
+ model: string;
32
+ usage?: TokenUsage;
33
+ finishReason?: string;
34
+ reasoningContent?: string;
35
+ responseTime?: number;
36
+ }
37
+ /**
38
+ * Provider error information
39
+ */
40
+ export interface ProviderErrorInfo {
41
+ provider: ProviderType;
42
+ code: string;
43
+ message: string;
44
+ retryable: boolean;
45
+ statusCode?: number;
46
+ }
47
+ /**
48
+ * Provider interface - all providers must implement this
49
+ */
50
+ export interface IProvider {
51
+ readonly name: ProviderType;
52
+ /**
53
+ * Check if the provider is configured with valid credentials
54
+ */
55
+ isConfigured(): boolean;
56
+ /**
57
+ * Create a chat completion
58
+ */
59
+ createCompletion(messages: ProviderMessage[], options: CompletionOptions): Promise<ProviderResponse>;
60
+ }
61
+ /**
62
+ * Model configuration
63
+ */
64
+ export interface ModelConfig {
65
+ provider: ProviderType;
66
+ apiModel: string;
67
+ maxTokens: number;
68
+ supportsSystemPrompt: boolean;
69
+ isReasoning: boolean;
70
+ reasoningEffort?: 'low' | 'medium' | 'high';
71
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/providers/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,101 @@
1
+ import type { Message, ModelType } from '../types/index.js';
2
+ /**
3
+ * Serializable conversation for file storage
4
+ */
5
+ interface SerializedConversation {
6
+ id: string;
7
+ model: ModelType;
8
+ messages: Message[];
9
+ systemPrompt: string;
10
+ createdAt: string;
11
+ lastActivityAt: string;
12
+ }
13
+ /**
14
+ * Archived conversation with end reason
15
+ */
16
+ export interface ArchivedConversation extends SerializedConversation {
17
+ endedAt: string;
18
+ endReason: 'completed' | 'timeout' | 'manual' | 'session_restart';
19
+ }
20
+ /**
21
+ * Represents an active conversation
22
+ */
23
+ export interface Conversation {
24
+ id: string;
25
+ model: ModelType;
26
+ messages: Message[];
27
+ systemPrompt: string;
28
+ createdAt: Date;
29
+ lastActivityAt: Date;
30
+ }
31
+ /**
32
+ * Manages active conversations with file persistence
33
+ */
34
+ export declare class ConversationManager {
35
+ private conversations;
36
+ private cleanupInterval;
37
+ constructor();
38
+ /**
39
+ * Archive all active conversations from previous session
40
+ * Called on startup to clean up orphaned conversations
41
+ */
42
+ private archiveStaleFromPreviousSession;
43
+ /**
44
+ * Save conversations to file
45
+ */
46
+ private saveToFile;
47
+ /**
48
+ * Load conversations from file (static method for external use)
49
+ */
50
+ static loadFromFile(): Conversation[];
51
+ /**
52
+ * Load archived conversations from history file (static method for external use)
53
+ */
54
+ static loadHistoryFromFile(): ArchivedConversation[];
55
+ /**
56
+ * Archive a conversation to history file
57
+ */
58
+ private archiveConversation;
59
+ /**
60
+ * Create a new conversation
61
+ */
62
+ create(model: ModelType, systemPrompt: string): Conversation;
63
+ /**
64
+ * Get a conversation by ID
65
+ */
66
+ get(conversationId: string): Conversation;
67
+ /**
68
+ * Add a message to a conversation
69
+ */
70
+ addMessage(conversationId: string, role: Message['role'], content: string): void;
71
+ /**
72
+ * Check if conversation can continue
73
+ */
74
+ canContinue(conversationId: string): boolean;
75
+ /**
76
+ * Get message count for a conversation
77
+ */
78
+ getMessageCount(conversationId: string): number;
79
+ /**
80
+ * End a conversation (archives it to history)
81
+ */
82
+ end(conversationId: string): number;
83
+ /**
84
+ * List all active conversations
85
+ */
86
+ listActive(): Conversation[];
87
+ /**
88
+ * Start periodic cleanup of stale conversations
89
+ */
90
+ private startCleanup;
91
+ /**
92
+ * Clean up stale conversations (archives them to history)
93
+ */
94
+ private cleanupStale;
95
+ /**
96
+ * Stop the cleanup interval
97
+ */
98
+ destroy(): void;
99
+ }
100
+ export declare const conversationManager: ConversationManager;
101
+ export {};
@@ -0,0 +1,275 @@
1
+ import { v4 as uuidv4 } from 'uuid';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { ConversationError } from '../utils/index.js';
6
+ import { MAX_MESSAGES_LIMIT, CONVERSATION_TIMEOUT_MS } from '../config/index.js';
7
+ // Get __dirname equivalent in ES modules
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+ // Conversations file path (in config directory)
11
+ const CONVERSATIONS_FILE = path.join(__dirname, '../../config/conversations.json');
12
+ const HISTORY_FILE = path.join(__dirname, '../../config/conversation_history.json');
13
+ /**
14
+ * Manages active conversations with file persistence
15
+ */
16
+ export class ConversationManager {
17
+ conversations = new Map();
18
+ cleanupInterval = null;
19
+ constructor() {
20
+ // Archive any leftover active conversations from previous session
21
+ this.archiveStaleFromPreviousSession();
22
+ // Start cleanup interval
23
+ this.startCleanup();
24
+ }
25
+ /**
26
+ * Archive all active conversations from previous session
27
+ * Called on startup to clean up orphaned conversations
28
+ */
29
+ archiveStaleFromPreviousSession() {
30
+ try {
31
+ if (!fs.existsSync(CONVERSATIONS_FILE)) {
32
+ return;
33
+ }
34
+ const data = fs.readFileSync(CONVERSATIONS_FILE, 'utf-8');
35
+ const serialized = JSON.parse(data);
36
+ if (serialized.length === 0) {
37
+ return;
38
+ }
39
+ console.log(`[ConversationManager] Archiving ${serialized.length} conversations from previous session`);
40
+ // Archive each conversation
41
+ for (const conv of serialized) {
42
+ const conversation = {
43
+ id: conv.id,
44
+ model: conv.model,
45
+ messages: conv.messages,
46
+ systemPrompt: conv.systemPrompt,
47
+ createdAt: new Date(conv.createdAt),
48
+ lastActivityAt: new Date(conv.lastActivityAt),
49
+ };
50
+ this.archiveConversation(conversation, 'session_restart');
51
+ }
52
+ // Clear the active conversations file
53
+ fs.writeFileSync(CONVERSATIONS_FILE, '[]', 'utf-8');
54
+ }
55
+ catch (error) {
56
+ console.error('[ConversationManager] Failed to archive previous session conversations:', error);
57
+ }
58
+ }
59
+ /**
60
+ * Save conversations to file
61
+ */
62
+ saveToFile() {
63
+ try {
64
+ const data = Array.from(this.conversations.values()).map(conv => ({
65
+ id: conv.id,
66
+ model: conv.model,
67
+ messages: conv.messages,
68
+ systemPrompt: conv.systemPrompt,
69
+ createdAt: conv.createdAt.toISOString(),
70
+ lastActivityAt: conv.lastActivityAt.toISOString(),
71
+ }));
72
+ // Ensure config directory exists
73
+ const configDir = path.dirname(CONVERSATIONS_FILE);
74
+ if (!fs.existsSync(configDir)) {
75
+ fs.mkdirSync(configDir, { recursive: true });
76
+ }
77
+ fs.writeFileSync(CONVERSATIONS_FILE, JSON.stringify(data, null, 2), 'utf-8');
78
+ }
79
+ catch (error) {
80
+ console.error('Failed to save conversations:', error);
81
+ }
82
+ }
83
+ /**
84
+ * Load conversations from file (static method for external use)
85
+ */
86
+ static loadFromFile() {
87
+ try {
88
+ if (!fs.existsSync(CONVERSATIONS_FILE)) {
89
+ return [];
90
+ }
91
+ const data = fs.readFileSync(CONVERSATIONS_FILE, 'utf-8');
92
+ const serialized = JSON.parse(data);
93
+ return serialized.map(conv => ({
94
+ id: conv.id,
95
+ model: conv.model,
96
+ messages: conv.messages,
97
+ systemPrompt: conv.systemPrompt,
98
+ createdAt: new Date(conv.createdAt),
99
+ lastActivityAt: new Date(conv.lastActivityAt),
100
+ }));
101
+ }
102
+ catch (error) {
103
+ console.error('Failed to load conversations:', error);
104
+ return [];
105
+ }
106
+ }
107
+ /**
108
+ * Load archived conversations from history file (static method for external use)
109
+ */
110
+ static loadHistoryFromFile() {
111
+ try {
112
+ if (!fs.existsSync(HISTORY_FILE)) {
113
+ return [];
114
+ }
115
+ const data = fs.readFileSync(HISTORY_FILE, 'utf-8');
116
+ return JSON.parse(data);
117
+ }
118
+ catch (error) {
119
+ console.error('Failed to load conversation history:', error);
120
+ return [];
121
+ }
122
+ }
123
+ /**
124
+ * Archive a conversation to history file
125
+ */
126
+ archiveConversation(conversation, reason) {
127
+ try {
128
+ // Load existing history
129
+ let history = [];
130
+ if (fs.existsSync(HISTORY_FILE)) {
131
+ const data = fs.readFileSync(HISTORY_FILE, 'utf-8');
132
+ history = JSON.parse(data);
133
+ }
134
+ // Add archived conversation
135
+ const archived = {
136
+ id: conversation.id,
137
+ model: conversation.model,
138
+ messages: conversation.messages,
139
+ systemPrompt: conversation.systemPrompt,
140
+ createdAt: conversation.createdAt.toISOString(),
141
+ lastActivityAt: conversation.lastActivityAt.toISOString(),
142
+ endedAt: new Date().toISOString(),
143
+ endReason: reason,
144
+ };
145
+ history.unshift(archived); // Add to beginning (newest first)
146
+ // Keep only last 100 conversations
147
+ if (history.length > 100) {
148
+ history = history.slice(0, 100);
149
+ }
150
+ // Ensure config directory exists
151
+ const configDir = path.dirname(HISTORY_FILE);
152
+ if (!fs.existsSync(configDir)) {
153
+ fs.mkdirSync(configDir, { recursive: true });
154
+ }
155
+ fs.writeFileSync(HISTORY_FILE, JSON.stringify(history, null, 2), 'utf-8');
156
+ }
157
+ catch (error) {
158
+ console.error('Failed to archive conversation:', error);
159
+ }
160
+ }
161
+ /**
162
+ * Create a new conversation
163
+ */
164
+ create(model, systemPrompt) {
165
+ const conversation = {
166
+ id: uuidv4(),
167
+ model,
168
+ messages: [],
169
+ systemPrompt,
170
+ createdAt: new Date(),
171
+ lastActivityAt: new Date(),
172
+ };
173
+ this.conversations.set(conversation.id, conversation);
174
+ this.saveToFile();
175
+ return conversation;
176
+ }
177
+ /**
178
+ * Get a conversation by ID
179
+ */
180
+ get(conversationId) {
181
+ const conversation = this.conversations.get(conversationId);
182
+ if (!conversation) {
183
+ throw new ConversationError(`Conversation not found: ${conversationId}`, conversationId);
184
+ }
185
+ return conversation;
186
+ }
187
+ /**
188
+ * Add a message to a conversation
189
+ */
190
+ addMessage(conversationId, role, content) {
191
+ const conversation = this.get(conversationId);
192
+ // Check message limit
193
+ if (conversation.messages.length >= MAX_MESSAGES_LIMIT * 2) {
194
+ throw new ConversationError(`Maximum message limit (${MAX_MESSAGES_LIMIT} exchanges) reached`, conversationId);
195
+ }
196
+ conversation.messages.push({ role, content });
197
+ conversation.lastActivityAt = new Date();
198
+ this.saveToFile();
199
+ }
200
+ /**
201
+ * Check if conversation can continue
202
+ */
203
+ canContinue(conversationId) {
204
+ const conversation = this.get(conversationId);
205
+ // Each exchange = 1 user + 1 assistant message = 2 messages
206
+ // maxMessages represents exchanges, so multiply by 2
207
+ return conversation.messages.length < MAX_MESSAGES_LIMIT * 2;
208
+ }
209
+ /**
210
+ * Get message count for a conversation
211
+ */
212
+ getMessageCount(conversationId) {
213
+ const conversation = this.get(conversationId);
214
+ return conversation.messages.length;
215
+ }
216
+ /**
217
+ * End a conversation (archives it to history)
218
+ */
219
+ end(conversationId) {
220
+ const conversation = this.get(conversationId);
221
+ const totalMessages = conversation.messages.length;
222
+ // Archive before deleting
223
+ this.archiveConversation(conversation, 'completed');
224
+ this.conversations.delete(conversationId);
225
+ this.saveToFile();
226
+ return totalMessages;
227
+ }
228
+ /**
229
+ * List all active conversations
230
+ */
231
+ listActive() {
232
+ return Array.from(this.conversations.values());
233
+ }
234
+ /**
235
+ * Start periodic cleanup of stale conversations
236
+ */
237
+ startCleanup() {
238
+ this.cleanupInterval = setInterval(() => {
239
+ this.cleanupStale();
240
+ }, 60000); // Check every minute
241
+ }
242
+ /**
243
+ * Clean up stale conversations (archives them to history)
244
+ */
245
+ cleanupStale() {
246
+ const now = Date.now();
247
+ let changed = false;
248
+ for (const [id, conversation] of this.conversations) {
249
+ const age = now - conversation.lastActivityAt.getTime();
250
+ if (age > CONVERSATION_TIMEOUT_MS) {
251
+ // Archive before deleting
252
+ this.archiveConversation(conversation, 'timeout');
253
+ this.conversations.delete(id);
254
+ changed = true;
255
+ }
256
+ }
257
+ if (changed) {
258
+ this.saveToFile();
259
+ }
260
+ }
261
+ /**
262
+ * Stop the cleanup interval
263
+ */
264
+ destroy() {
265
+ if (this.cleanupInterval) {
266
+ clearInterval(this.cleanupInterval);
267
+ this.cleanupInterval = null;
268
+ }
269
+ this.conversations.clear();
270
+ this.saveToFile();
271
+ }
272
+ }
273
+ // Singleton instance
274
+ export const conversationManager = new ConversationManager();
275
+ //# sourceMappingURL=conversation.js.map