ydc-mcp-server 1.6.1
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.md +140 -0
- package/README_JA.md +138 -0
- package/README_ZH_CN.md +138 -0
- package/README_ZH_TW.md +138 -0
- package/index.js +604 -0
- package/lib/advanced-versions.js +113 -0
- package/lib/api-client.js +134 -0
- package/lib/auth-middleware.js +44 -0
- package/lib/conversation-store.js +271 -0
- package/lib/openai-mapper.js +215 -0
- package/lib/routes/chat.js +199 -0
- package/lib/routes/conversations.js +94 -0
- package/lib/routes/health.js +31 -0
- package/lib/routes/models.js +111 -0
- package/openai-server.js +93 -0
- package/package.json +62 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advanced Agent Version System
|
|
3
|
+
* Provides specialized configurations for different use cases
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const ADVANCED_VERSIONS = {
|
|
7
|
+
'advanced-1.0-medium': {
|
|
8
|
+
verbosity: 'medium',
|
|
9
|
+
max_workflow_steps: 1,
|
|
10
|
+
timeout: 120000,
|
|
11
|
+
tools: []
|
|
12
|
+
},
|
|
13
|
+
'advanced-1.0-high': {
|
|
14
|
+
verbosity: 'high',
|
|
15
|
+
max_workflow_steps: 1,
|
|
16
|
+
timeout: 120000,
|
|
17
|
+
tools: []
|
|
18
|
+
},
|
|
19
|
+
'advanced-2.0-medium': {
|
|
20
|
+
verbosity: 'medium',
|
|
21
|
+
max_workflow_steps: 2,
|
|
22
|
+
timeout: 180000,
|
|
23
|
+
tools: []
|
|
24
|
+
},
|
|
25
|
+
'advanced-2.0-high': {
|
|
26
|
+
verbosity: 'high',
|
|
27
|
+
max_workflow_steps: 2,
|
|
28
|
+
timeout: 180000,
|
|
29
|
+
tools: []
|
|
30
|
+
},
|
|
31
|
+
'advanced-3.0-medium': {
|
|
32
|
+
verbosity: 'medium',
|
|
33
|
+
max_workflow_steps: 5,
|
|
34
|
+
timeout: 180000,
|
|
35
|
+
tools: [{ type: 'compute' }]
|
|
36
|
+
},
|
|
37
|
+
'advanced-3.0-high': {
|
|
38
|
+
verbosity: 'high',
|
|
39
|
+
max_workflow_steps: 5,
|
|
40
|
+
timeout: 180000,
|
|
41
|
+
tools: [{ type: 'compute' }]
|
|
42
|
+
},
|
|
43
|
+
'advanced-4.0-medium': {
|
|
44
|
+
verbosity: 'medium',
|
|
45
|
+
max_workflow_steps: 6,
|
|
46
|
+
timeout: 180000,
|
|
47
|
+
tools: [{ type: 'research', search_effort: 'medium', report_verbosity: 'medium' }]
|
|
48
|
+
},
|
|
49
|
+
'advanced-4.0-high': {
|
|
50
|
+
verbosity: 'high',
|
|
51
|
+
max_workflow_steps: 6,
|
|
52
|
+
timeout: 180000,
|
|
53
|
+
tools: [{ type: 'research', search_effort: 'high', report_verbosity: 'high' }]
|
|
54
|
+
},
|
|
55
|
+
'advanced-4.5-medium-research': {
|
|
56
|
+
verbosity: 'medium',
|
|
57
|
+
max_workflow_steps: 9,
|
|
58
|
+
timeout: 300000,
|
|
59
|
+
tools: [
|
|
60
|
+
{ type: 'compute' },
|
|
61
|
+
{ type: 'research', search_effort: 'medium', report_verbosity: 'medium' }
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
'advanced-4.5-high-research': {
|
|
65
|
+
verbosity: 'high',
|
|
66
|
+
max_workflow_steps: 9,
|
|
67
|
+
timeout: 300000,
|
|
68
|
+
tools: [
|
|
69
|
+
{ type: 'compute' },
|
|
70
|
+
{ type: 'research', search_effort: 'high', report_verbosity: 'high' }
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export function getAdvancedVersion(versionName) {
|
|
76
|
+
return ADVANCED_VERSIONS[versionName] || null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function isAdvancedVersion(modelName) {
|
|
80
|
+
return modelName in ADVANCED_VERSIONS;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function getDefaultAdvancedVersion(temperature = 0.7) {
|
|
84
|
+
return temperature <= 0.5 ? 'advanced-3.0-medium' : 'advanced-3.0-high';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function adjustWorkflowSteps(baseSteps, temperature) {
|
|
88
|
+
const adjustment = Math.round((temperature - 0.5) * 4);
|
|
89
|
+
return Math.max(1, Math.min(20, baseSteps + adjustment));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function listAdvancedVersions() {
|
|
93
|
+
return Object.keys(ADVANCED_VERSIONS);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function getVersionInfo(versionName) {
|
|
97
|
+
const config = ADVANCED_VERSIONS[versionName];
|
|
98
|
+
if (!config) return null;
|
|
99
|
+
|
|
100
|
+
const toolNames = config.tools.map(t => {
|
|
101
|
+
if (t.type === 'research') return `research(${t.search_effort})`;
|
|
102
|
+
return t.type;
|
|
103
|
+
}).join(', ') || 'none';
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
name: versionName,
|
|
107
|
+
verbosity: config.verbosity,
|
|
108
|
+
max_workflow_steps: config.max_workflow_steps,
|
|
109
|
+
timeout_seconds: config.timeout / 1000,
|
|
110
|
+
tools: toolNames,
|
|
111
|
+
description: `${config.verbosity} verbosity, ${config.max_workflow_steps} steps, ${config.timeout/1000}s timeout`
|
|
112
|
+
};
|
|
113
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* You.com API Client Module
|
|
3
|
+
* Shared API calling logic for MCP and OpenAI servers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const API_BASE = 'https://api.you.com/v1/agents/runs';
|
|
7
|
+
|
|
8
|
+
// Agent type configurations
|
|
9
|
+
export const AGENT_TYPES = {
|
|
10
|
+
'express': { agent: 'express', description: 'Fast AI answers with web search' },
|
|
11
|
+
'research': { agent: 'research', description: 'In-depth research and analysis' },
|
|
12
|
+
'advanced': { agent: 'advanced', description: 'Complex reasoning with tools' },
|
|
13
|
+
'advanced-3.0-high': { agent: 'advanced', verbosity: 'high', max_workflow_steps: 15 },
|
|
14
|
+
'advanced-3.0-medium': { agent: 'advanced', verbosity: 'medium', max_workflow_steps: 10 },
|
|
15
|
+
'advanced-4.0-high': { agent: 'advanced', verbosity: 'high', max_workflow_steps: 20 },
|
|
16
|
+
'advanced-4.5-research': {
|
|
17
|
+
agent: 'advanced',
|
|
18
|
+
verbosity: 'high',
|
|
19
|
+
max_workflow_steps: 20,
|
|
20
|
+
tools: [{ type: 'research', search_effort: 'high', report_verbosity: 'high' }, { type: 'compute' }]
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Call You.com API
|
|
26
|
+
*/
|
|
27
|
+
export async function callYouApi(apiKey, requestBody, options = {}) {
|
|
28
|
+
const { timeout = 300000 } = options;
|
|
29
|
+
|
|
30
|
+
const controller = new AbortController();
|
|
31
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const response = await fetch(API_BASE, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: {
|
|
37
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
38
|
+
'Content-Type': 'application/json',
|
|
39
|
+
},
|
|
40
|
+
body: JSON.stringify(requestBody),
|
|
41
|
+
signal: controller.signal,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
clearTimeout(timeoutId);
|
|
45
|
+
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
const errorText = await response.text();
|
|
48
|
+
throw new Error(`API error: ${response.status} ${errorText}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return response;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
clearTimeout(timeoutId);
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Extract text from You.com response
|
|
60
|
+
*/
|
|
61
|
+
export function extractText(data) {
|
|
62
|
+
if (data.output && Array.isArray(data.output)) {
|
|
63
|
+
return data.output
|
|
64
|
+
.filter(item => item.type === 'message.answer')
|
|
65
|
+
.map(item => item.text)
|
|
66
|
+
.join('\n\n');
|
|
67
|
+
}
|
|
68
|
+
return JSON.stringify(data, null, 2);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Build conversation input from messages
|
|
73
|
+
*/
|
|
74
|
+
export function buildConversationInput(messages) {
|
|
75
|
+
let input = '';
|
|
76
|
+
let systemPrompt = '';
|
|
77
|
+
const conversationHistory = [];
|
|
78
|
+
|
|
79
|
+
messages.forEach(msg => {
|
|
80
|
+
if (msg.role === 'system') {
|
|
81
|
+
systemPrompt = msg.content;
|
|
82
|
+
} else if (msg.role === 'user') {
|
|
83
|
+
conversationHistory.push(`User: ${msg.content}`);
|
|
84
|
+
} else if (msg.role === 'assistant') {
|
|
85
|
+
conversationHistory.push(`Assistant: ${msg.content}`);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (systemPrompt) {
|
|
90
|
+
input = `[System Instructions]\n${systemPrompt}\n\n`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (conversationHistory.length > 1) {
|
|
94
|
+
input += `[Conversation History]\n${conversationHistory.slice(0, -1).join('\n\n')}\n\n`;
|
|
95
|
+
input += `[Current Message]\n${conversationHistory[conversationHistory.length - 1].replace(/^User: /, '')}`;
|
|
96
|
+
} else if (conversationHistory.length === 1) {
|
|
97
|
+
input += conversationHistory[0].replace(/^User: /, '');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return input;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Build request body for agent call
|
|
105
|
+
*/
|
|
106
|
+
export function buildAgentRequest(agentType, input, options = {}) {
|
|
107
|
+
const { tools, verbosity, max_workflow_steps, stream = false } = options;
|
|
108
|
+
const agentConfig = AGENT_TYPES[agentType] || AGENT_TYPES['advanced-3.0-high'];
|
|
109
|
+
const isExpressAgent = agentType === 'express' || agentConfig.agent === 'express';
|
|
110
|
+
|
|
111
|
+
if (isExpressAgent) {
|
|
112
|
+
return {
|
|
113
|
+
agent: 'express',
|
|
114
|
+
input,
|
|
115
|
+
stream,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
agent: agentConfig.agent || 'advanced',
|
|
121
|
+
input,
|
|
122
|
+
stream,
|
|
123
|
+
verbosity: verbosity || agentConfig.verbosity || 'high',
|
|
124
|
+
tools: tools || agentConfig.tools || [
|
|
125
|
+
{ type: 'research', search_effort: 'high', report_verbosity: 'high' },
|
|
126
|
+
{ type: 'compute' }
|
|
127
|
+
],
|
|
128
|
+
workflow_config: {
|
|
129
|
+
max_workflow_steps: max_workflow_steps || agentConfig.max_workflow_steps || 15
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export { API_BASE };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication Middleware Module
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Access token configuration
|
|
6
|
+
const ACCESS_TOKENS_RAW = process.env.YDC_OPENAI_ACCESS_TOKENS || '';
|
|
7
|
+
const ACCESS_TOKENS = ACCESS_TOKENS_RAW.split(',').map(t => t.trim()).filter(t => t);
|
|
8
|
+
const REQUIRE_TOKEN_AUTH = ACCESS_TOKENS.length > 0;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Authentication middleware for Express
|
|
12
|
+
*/
|
|
13
|
+
export function authenticate(req, res, next) {
|
|
14
|
+
const authHeader = req.headers.authorization;
|
|
15
|
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
16
|
+
return res.status(401).json({
|
|
17
|
+
error: {
|
|
18
|
+
message: 'Invalid authentication credentials',
|
|
19
|
+
type: 'invalid_request_error',
|
|
20
|
+
code: 'invalid_api_key'
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (REQUIRE_TOKEN_AUTH) {
|
|
26
|
+
const token = authHeader.slice(7);
|
|
27
|
+
if (!ACCESS_TOKENS.includes(token)) {
|
|
28
|
+
return res.status(401).json({
|
|
29
|
+
error: {
|
|
30
|
+
message: 'Invalid access token',
|
|
31
|
+
type: 'invalid_request_error',
|
|
32
|
+
code: 'invalid_access_token'
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
next();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const authConfig = {
|
|
42
|
+
REQUIRE_TOKEN_AUTH,
|
|
43
|
+
ACCESS_TOKENS_COUNT: ACCESS_TOKENS.length
|
|
44
|
+
};
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conversation Store Module
|
|
3
|
+
* Supports SQLite (persistent) and Memory (in-memory) storage
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import Database from 'better-sqlite3';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import { dirname, join } from 'path';
|
|
9
|
+
import crypto from 'crypto';
|
|
10
|
+
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = dirname(__filename);
|
|
13
|
+
|
|
14
|
+
// Configuration
|
|
15
|
+
const STORE_TYPE = process.env.YDC_CONVERSATION_STORE || 'sqlite';
|
|
16
|
+
const DB_PATH = process.env.YDC_CONVERSATION_DB_PATH || join(__dirname, '..', 'conversations.db');
|
|
17
|
+
const CONVERSATION_TTL = 24 * 60 * 60 * 1000; // 24 hours
|
|
18
|
+
const MAX_CONVERSATIONS = 1000;
|
|
19
|
+
const MAX_MESSAGES_PER_CONVERSATION = 100;
|
|
20
|
+
|
|
21
|
+
// Memory store (fallback)
|
|
22
|
+
const memoryStore = new Map();
|
|
23
|
+
|
|
24
|
+
// SQLite database
|
|
25
|
+
let db = null;
|
|
26
|
+
|
|
27
|
+
function initDatabase() {
|
|
28
|
+
if (STORE_TYPE === 'memory') {
|
|
29
|
+
console.log('📦 Using in-memory conversation store');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
db = new Database(DB_PATH);
|
|
35
|
+
|
|
36
|
+
db.exec(`
|
|
37
|
+
CREATE TABLE IF NOT EXISTS conversations (
|
|
38
|
+
id TEXT PRIMARY KEY,
|
|
39
|
+
metadata TEXT DEFAULT '{}',
|
|
40
|
+
created_at INTEGER NOT NULL,
|
|
41
|
+
updated_at INTEGER NOT NULL
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
45
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
46
|
+
conversation_id TEXT NOT NULL,
|
|
47
|
+
role TEXT NOT NULL,
|
|
48
|
+
content TEXT NOT NULL,
|
|
49
|
+
timestamp INTEGER NOT NULL,
|
|
50
|
+
FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_messages_conversation ON messages(conversation_id);
|
|
54
|
+
CREATE INDEX IF NOT EXISTS idx_conversations_updated ON conversations(updated_at);
|
|
55
|
+
`);
|
|
56
|
+
|
|
57
|
+
console.log(`📦 Using SQLite conversation store: ${DB_PATH}`);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error('⚠️ Failed to initialize SQLite, falling back to memory store:', error.message);
|
|
60
|
+
db = null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Initialize database
|
|
65
|
+
initDatabase();
|
|
66
|
+
|
|
67
|
+
export function generateConversationId() {
|
|
68
|
+
return crypto.randomUUID();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function getConversation(conversationId) {
|
|
72
|
+
if (!conversationId) return null;
|
|
73
|
+
|
|
74
|
+
if (STORE_TYPE === 'memory' || !db) {
|
|
75
|
+
if (!memoryStore.has(conversationId)) return null;
|
|
76
|
+
const conv = memoryStore.get(conversationId);
|
|
77
|
+
conv.updatedAt = Date.now();
|
|
78
|
+
return conv;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const conv = db.prepare('SELECT * FROM conversations WHERE id = ?').get(conversationId);
|
|
82
|
+
if (!conv) return null;
|
|
83
|
+
|
|
84
|
+
const messages = db.prepare('SELECT role, content, timestamp FROM messages WHERE conversation_id = ? ORDER BY id').all(conversationId);
|
|
85
|
+
db.prepare('UPDATE conversations SET updated_at = ? WHERE id = ?').run(Date.now(), conversationId);
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
id: conv.id,
|
|
89
|
+
messages,
|
|
90
|
+
metadata: JSON.parse(conv.metadata || '{}'),
|
|
91
|
+
createdAt: conv.created_at,
|
|
92
|
+
updatedAt: Date.now()
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function createConversation(conversationId = null, metadata = {}) {
|
|
97
|
+
const id = conversationId || generateConversationId();
|
|
98
|
+
const now = Date.now();
|
|
99
|
+
|
|
100
|
+
if (STORE_TYPE === 'memory' || !db) {
|
|
101
|
+
const conv = { id, messages: [], createdAt: now, updatedAt: now, metadata };
|
|
102
|
+
memoryStore.set(id, conv);
|
|
103
|
+
return conv;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
db.prepare('INSERT OR REPLACE INTO conversations (id, metadata, created_at, updated_at) VALUES (?, ?, ?, ?)')
|
|
107
|
+
.run(id, JSON.stringify(metadata), now, now);
|
|
108
|
+
|
|
109
|
+
return { id, messages: [], createdAt: now, updatedAt: now, metadata };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function addMessageToConversation(conversationId, role, content) {
|
|
113
|
+
const now = Date.now();
|
|
114
|
+
|
|
115
|
+
if (STORE_TYPE === 'memory' || !db) {
|
|
116
|
+
let conv = memoryStore.get(conversationId);
|
|
117
|
+
if (!conv) {
|
|
118
|
+
conv = createConversation(conversationId);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (conv.messages.length >= MAX_MESSAGES_PER_CONVERSATION) {
|
|
122
|
+
const systemMsg = conv.messages.find(m => m.role === 'system');
|
|
123
|
+
conv.messages = systemMsg ? [systemMsg, ...conv.messages.slice(-MAX_MESSAGES_PER_CONVERSATION + 2)] : conv.messages.slice(-MAX_MESSAGES_PER_CONVERSATION + 1);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
conv.messages.push({ role, content, timestamp: now });
|
|
127
|
+
conv.updatedAt = now;
|
|
128
|
+
return conv;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const existing = db.prepare('SELECT id FROM conversations WHERE id = ?').get(conversationId);
|
|
132
|
+
if (!existing) {
|
|
133
|
+
createConversation(conversationId);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const count = db.prepare('SELECT COUNT(*) as count FROM messages WHERE conversation_id = ?').get(conversationId).count;
|
|
137
|
+
if (count >= MAX_MESSAGES_PER_CONVERSATION) {
|
|
138
|
+
const systemMsg = db.prepare("SELECT id FROM messages WHERE conversation_id = ? AND role = 'system' LIMIT 1").get(conversationId);
|
|
139
|
+
const deleteCount = count - MAX_MESSAGES_PER_CONVERSATION + 2;
|
|
140
|
+
|
|
141
|
+
if (systemMsg) {
|
|
142
|
+
db.prepare('DELETE FROM messages WHERE conversation_id = ? AND id != ? ORDER BY id LIMIT ?')
|
|
143
|
+
.run(conversationId, systemMsg.id, deleteCount);
|
|
144
|
+
} else {
|
|
145
|
+
db.prepare('DELETE FROM messages WHERE conversation_id = ? ORDER BY id LIMIT ?')
|
|
146
|
+
.run(conversationId, deleteCount);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
db.prepare('INSERT INTO messages (conversation_id, role, content, timestamp) VALUES (?, ?, ?, ?)')
|
|
151
|
+
.run(conversationId, role, content, now);
|
|
152
|
+
|
|
153
|
+
db.prepare('UPDATE conversations SET updated_at = ? WHERE id = ?').run(now, conversationId);
|
|
154
|
+
|
|
155
|
+
return getConversation(conversationId);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function listAllConversations() {
|
|
159
|
+
if (STORE_TYPE === 'memory' || !db) {
|
|
160
|
+
const conversations = [];
|
|
161
|
+
for (const [id, conv] of memoryStore.entries()) {
|
|
162
|
+
conversations.push({
|
|
163
|
+
id,
|
|
164
|
+
message_count: conv.messages.length,
|
|
165
|
+
created_at: new Date(conv.createdAt).toISOString(),
|
|
166
|
+
updated_at: new Date(conv.updatedAt).toISOString(),
|
|
167
|
+
metadata: conv.metadata,
|
|
168
|
+
preview: conv.messages.slice(-1)[0]?.content?.substring(0, 100) || ''
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return conversations.sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const convs = db.prepare('SELECT * FROM conversations ORDER BY updated_at DESC').all();
|
|
175
|
+
return convs.map(conv => {
|
|
176
|
+
const lastMsg = db.prepare('SELECT content FROM messages WHERE conversation_id = ? ORDER BY id DESC LIMIT 1').get(conv.id);
|
|
177
|
+
const msgCount = db.prepare('SELECT COUNT(*) as count FROM messages WHERE conversation_id = ?').get(conv.id).count;
|
|
178
|
+
return {
|
|
179
|
+
id: conv.id,
|
|
180
|
+
message_count: msgCount,
|
|
181
|
+
created_at: new Date(conv.created_at).toISOString(),
|
|
182
|
+
updated_at: new Date(conv.updated_at).toISOString(),
|
|
183
|
+
metadata: JSON.parse(conv.metadata || '{}'),
|
|
184
|
+
preview: lastMsg?.content?.substring(0, 100) || ''
|
|
185
|
+
};
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function deleteConversation(conversationId) {
|
|
190
|
+
if (STORE_TYPE === 'memory' || !db) {
|
|
191
|
+
return memoryStore.delete(conversationId);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
db.prepare('DELETE FROM messages WHERE conversation_id = ?').run(conversationId);
|
|
195
|
+
const result = db.prepare('DELETE FROM conversations WHERE id = ?').run(conversationId);
|
|
196
|
+
return result.changes > 0;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function clearAllConversations() {
|
|
200
|
+
if (STORE_TYPE === 'memory' || !db) {
|
|
201
|
+
const count = memoryStore.size;
|
|
202
|
+
memoryStore.clear();
|
|
203
|
+
return count;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const count = db.prepare('SELECT COUNT(*) as count FROM conversations').get().count;
|
|
207
|
+
db.prepare('DELETE FROM messages').run();
|
|
208
|
+
db.prepare('DELETE FROM conversations').run();
|
|
209
|
+
return count;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function getConversationCount() {
|
|
213
|
+
if (STORE_TYPE === 'memory' || !db) {
|
|
214
|
+
return memoryStore.size;
|
|
215
|
+
}
|
|
216
|
+
return db.prepare('SELECT COUNT(*) as count FROM conversations').get().count;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function cleanupConversations() {
|
|
220
|
+
const now = Date.now();
|
|
221
|
+
const expireTime = now - CONVERSATION_TTL;
|
|
222
|
+
|
|
223
|
+
if (STORE_TYPE === 'memory' || !db) {
|
|
224
|
+
let deleted = 0;
|
|
225
|
+
for (const [id, conv] of memoryStore.entries()) {
|
|
226
|
+
if (conv.updatedAt < expireTime) {
|
|
227
|
+
memoryStore.delete(id);
|
|
228
|
+
deleted++;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (memoryStore.size > MAX_CONVERSATIONS) {
|
|
233
|
+
const sorted = [...memoryStore.entries()].sort((a, b) => a[1].updatedAt - b[1].updatedAt);
|
|
234
|
+
const toDelete = sorted.slice(0, memoryStore.size - MAX_CONVERSATIONS);
|
|
235
|
+
toDelete.forEach(([id]) => memoryStore.delete(id));
|
|
236
|
+
deleted += toDelete.length;
|
|
237
|
+
}
|
|
238
|
+
return deleted;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const expiredConvs = db.prepare('SELECT id FROM conversations WHERE updated_at < ?').all(expireTime);
|
|
242
|
+
for (const conv of expiredConvs) {
|
|
243
|
+
db.prepare('DELETE FROM messages WHERE conversation_id = ?').run(conv.id);
|
|
244
|
+
}
|
|
245
|
+
let result = db.prepare('DELETE FROM conversations WHERE updated_at < ?').run(expireTime);
|
|
246
|
+
let deleted = result.changes;
|
|
247
|
+
|
|
248
|
+
const count = db.prepare('SELECT COUNT(*) as count FROM conversations').get().count;
|
|
249
|
+
if (count > MAX_CONVERSATIONS) {
|
|
250
|
+
const toDelete = db.prepare('SELECT id FROM conversations ORDER BY updated_at ASC LIMIT ?').all(count - MAX_CONVERSATIONS);
|
|
251
|
+
for (const conv of toDelete) {
|
|
252
|
+
db.prepare('DELETE FROM messages WHERE conversation_id = ?').run(conv.id);
|
|
253
|
+
db.prepare('DELETE FROM conversations WHERE id = ?').run(conv.id);
|
|
254
|
+
deleted++;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return deleted;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Run cleanup every hour
|
|
262
|
+
setInterval(cleanupConversations, 60 * 60 * 1000);
|
|
263
|
+
|
|
264
|
+
// Export config for health check
|
|
265
|
+
export const storeConfig = {
|
|
266
|
+
STORE_TYPE,
|
|
267
|
+
DB_PATH,
|
|
268
|
+
MAX_CONVERSATIONS,
|
|
269
|
+
CONVERSATION_TTL,
|
|
270
|
+
isDbConnected: () => !!db
|
|
271
|
+
};
|