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.
- package/LICENSE +21 -0
- package/README.md +140 -0
- package/dist/api/index.d.ts +27 -0
- package/dist/api/index.js +213 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/middleware/security.d.ts +6 -0
- package/dist/api/middleware/security.js +28 -0
- package/dist/api/middleware/security.js.map +1 -0
- package/dist/api/routes/chat.d.ts +2 -0
- package/dist/api/routes/chat.js +61 -0
- package/dist/api/routes/chat.js.map +1 -0
- package/dist/api/routes/config.d.ts +2 -0
- package/dist/api/routes/config.js +81 -0
- package/dist/api/routes/config.js.map +1 -0
- package/dist/api/routes/providers.d.ts +2 -0
- package/dist/api/routes/providers.js +225 -0
- package/dist/api/routes/providers.js.map +1 -0
- package/dist/api/standalone-server.d.ts +12 -0
- package/dist/api/standalone-server.js +91 -0
- package/dist/api/standalone-server.js.map +1 -0
- package/dist/config/defaults.d.ts +17 -0
- package/dist/config/defaults.js +30 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/encryption.d.ts +12 -0
- package/dist/config/encryption.js +85 -0
- package/dist/config/encryption.js.map +1 -0
- package/dist/config/index.d.ts +5 -0
- package/dist/config/index.js +6 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/manager.d.ts +62 -0
- package/dist/config/manager.js +186 -0
- package/dist/config/manager.js.map +1 -0
- package/dist/config/prompts.d.ts +10 -0
- package/dist/config/prompts.js +140 -0
- package/dist/config/prompts.js.map +1 -0
- package/dist/config/schema.d.ts +141 -0
- package/dist/config/schema.js +54 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +224 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/base.d.ts +44 -0
- package/dist/providers/base.js +84 -0
- package/dist/providers/base.js.map +1 -0
- package/dist/providers/deepseek.d.ts +30 -0
- package/dist/providers/deepseek.js +148 -0
- package/dist/providers/deepseek.js.map +1 -0
- package/dist/providers/index.d.ts +5 -0
- package/dist/providers/index.js +8 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/openai.d.ts +30 -0
- package/dist/providers/openai.js +123 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/registry.d.ts +37 -0
- package/dist/providers/registry.js +65 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/providers/types.d.ts +71 -0
- package/dist/providers/types.js +2 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/server/conversation.d.ts +101 -0
- package/dist/server/conversation.js +275 -0
- package/dist/server/conversation.js.map +1 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +3 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/tools/consult.d.ts +15 -0
- package/dist/server/tools/consult.js +164 -0
- package/dist/server/tools/consult.js.map +1 -0
- package/dist/server/tools/index.d.ts +1 -0
- package/dist/server/tools/index.js +2 -0
- package/dist/server/tools/index.js.map +1 -0
- package/dist/types/config.d.ts +20 -0
- package/dist/types/config.js +2 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.js +4 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/messages.d.ts +48 -0
- package/dist/types/messages.js +2 -0
- package/dist/types/messages.js.map +1 -0
- package/dist/types/models.d.ts +39 -0
- package/dist/types/models.js +66 -0
- package/dist/types/models.js.map +1 -0
- package/dist/ui/index.html +1044 -0
- package/dist/utils/errors.d.ts +32 -0
- package/dist/utils/errors.js +53 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +13 -0
- package/dist/utils/logger.js +73 -0
- package/dist/utils/logger.js.map +1 -0
- 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 @@
|
|
|
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
|