ydc-agent 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/README.md +217 -0
- package/README_JA.md +189 -0
- package/README_ZH_CN.md +189 -0
- package/README_ZH_TW.md +189 -0
- package/index.js +1160 -0
- package/lib/advanced-versions.js +113 -0
- package/lib/anthropic-mapper.js +255 -0
- package/lib/api-client.js +140 -0
- package/lib/auth-middleware.js +44 -0
- package/lib/conversation-store.js +358 -0
- package/lib/openai-mapper.js +215 -0
- package/lib/request-logger.js +132 -0
- package/lib/routes/anthropic-messages.js +269 -0
- package/lib/routes/chat.js +249 -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 +99 -0
- package/package.json +70 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conversation Store Module
|
|
3
|
+
* Supports SQLite (persistent via sql.js) and Memory (in-memory) storage
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import { dirname, join } from 'path';
|
|
8
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
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 (sql.js)
|
|
25
|
+
let db = null;
|
|
26
|
+
let SQL = null;
|
|
27
|
+
let dbInitialized = false;
|
|
28
|
+
|
|
29
|
+
// Save database to file periodically
|
|
30
|
+
let saveTimeout = null;
|
|
31
|
+
function scheduleSave() {
|
|
32
|
+
if (saveTimeout) clearTimeout(saveTimeout);
|
|
33
|
+
saveTimeout = setTimeout(() => {
|
|
34
|
+
if (db && STORE_TYPE === 'sqlite') {
|
|
35
|
+
try {
|
|
36
|
+
const data = db.export();
|
|
37
|
+
writeFileSync(DB_PATH, Buffer.from(data));
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('β οΈ Failed to save database:', error.message);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}, 1000); // Debounce saves by 1 second
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function initDatabase() {
|
|
46
|
+
if (dbInitialized) return;
|
|
47
|
+
dbInitialized = true;
|
|
48
|
+
|
|
49
|
+
if (STORE_TYPE === 'memory') {
|
|
50
|
+
console.log('π¦ Using in-memory conversation store');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
// Dynamic import sql.js
|
|
56
|
+
const initSqlJs = (await import('sql.js')).default;
|
|
57
|
+
SQL = await initSqlJs();
|
|
58
|
+
|
|
59
|
+
// Load existing database or create new one
|
|
60
|
+
if (existsSync(DB_PATH)) {
|
|
61
|
+
try {
|
|
62
|
+
const fileBuffer = readFileSync(DB_PATH);
|
|
63
|
+
db = new SQL.Database(fileBuffer);
|
|
64
|
+
console.log(`π¦ Loaded SQLite database: ${DB_PATH}`);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error('β οΈ Failed to load existing database, creating new one:', error.message);
|
|
67
|
+
db = new SQL.Database();
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
db = new SQL.Database();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Create tables
|
|
74
|
+
db.run(`
|
|
75
|
+
CREATE TABLE IF NOT EXISTS conversations (
|
|
76
|
+
id TEXT PRIMARY KEY,
|
|
77
|
+
metadata TEXT DEFAULT '{}',
|
|
78
|
+
created_at INTEGER NOT NULL,
|
|
79
|
+
updated_at INTEGER NOT NULL
|
|
80
|
+
)
|
|
81
|
+
`);
|
|
82
|
+
|
|
83
|
+
db.run(`
|
|
84
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
85
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
86
|
+
conversation_id TEXT NOT NULL,
|
|
87
|
+
role TEXT NOT NULL,
|
|
88
|
+
content TEXT NOT NULL,
|
|
89
|
+
timestamp INTEGER NOT NULL,
|
|
90
|
+
FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE
|
|
91
|
+
)
|
|
92
|
+
`);
|
|
93
|
+
|
|
94
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_messages_conversation ON messages(conversation_id)`);
|
|
95
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_conversations_updated ON conversations(updated_at)`);
|
|
96
|
+
|
|
97
|
+
// Save initial database
|
|
98
|
+
scheduleSave();
|
|
99
|
+
|
|
100
|
+
console.log(`π¦ Using SQLite conversation store: ${DB_PATH}`);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error('β οΈ Failed to initialize SQLite, falling back to memory store:', error.message);
|
|
103
|
+
db = null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Export init function for async initialization
|
|
108
|
+
export { initDatabase };
|
|
109
|
+
|
|
110
|
+
export function generateConversationId() {
|
|
111
|
+
return crypto.randomUUID();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function getConversation(conversationId) {
|
|
115
|
+
if (!conversationId) return null;
|
|
116
|
+
|
|
117
|
+
if (STORE_TYPE === 'memory' || !db) {
|
|
118
|
+
if (!memoryStore.has(conversationId)) return null;
|
|
119
|
+
const conv = memoryStore.get(conversationId);
|
|
120
|
+
conv.updatedAt = Date.now();
|
|
121
|
+
return conv;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const convResult = db.exec('SELECT * FROM conversations WHERE id = ?', [conversationId]);
|
|
125
|
+
if (!convResult.length || !convResult[0].values.length) return null;
|
|
126
|
+
|
|
127
|
+
const conv = convResult[0].values[0];
|
|
128
|
+
const [id, metadata, created_at, updated_at] = conv;
|
|
129
|
+
|
|
130
|
+
const messagesResult = db.exec('SELECT role, content, timestamp FROM messages WHERE conversation_id = ? ORDER BY id', [conversationId]);
|
|
131
|
+
const messages = messagesResult.length ? messagesResult[0].values.map(row => ({
|
|
132
|
+
role: row[0],
|
|
133
|
+
content: row[1],
|
|
134
|
+
timestamp: row[2]
|
|
135
|
+
})) : [];
|
|
136
|
+
|
|
137
|
+
db.run('UPDATE conversations SET updated_at = ? WHERE id = ?', [Date.now(), conversationId]);
|
|
138
|
+
scheduleSave();
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
id,
|
|
142
|
+
messages,
|
|
143
|
+
metadata: JSON.parse(metadata || '{}'),
|
|
144
|
+
createdAt: created_at,
|
|
145
|
+
updatedAt: Date.now()
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function createConversation(conversationId = null, metadata = {}) {
|
|
150
|
+
const id = conversationId || generateConversationId();
|
|
151
|
+
const now = Date.now();
|
|
152
|
+
|
|
153
|
+
if (STORE_TYPE === 'memory' || !db) {
|
|
154
|
+
const conv = { id, messages: [], createdAt: now, updatedAt: now, metadata };
|
|
155
|
+
memoryStore.set(id, conv);
|
|
156
|
+
return conv;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
db.run('INSERT OR REPLACE INTO conversations (id, metadata, created_at, updated_at) VALUES (?, ?, ?, ?)',
|
|
160
|
+
[id, JSON.stringify(metadata), now, now]);
|
|
161
|
+
scheduleSave();
|
|
162
|
+
|
|
163
|
+
return { id, messages: [], createdAt: now, updatedAt: now, metadata };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function addMessageToConversation(conversationId, role, content) {
|
|
167
|
+
const now = Date.now();
|
|
168
|
+
|
|
169
|
+
if (STORE_TYPE === 'memory' || !db) {
|
|
170
|
+
let conv = memoryStore.get(conversationId);
|
|
171
|
+
if (!conv) {
|
|
172
|
+
conv = createConversation(conversationId);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (conv.messages.length >= MAX_MESSAGES_PER_CONVERSATION) {
|
|
176
|
+
const systemMsg = conv.messages.find(m => m.role === 'system');
|
|
177
|
+
conv.messages = systemMsg ? [systemMsg, ...conv.messages.slice(-MAX_MESSAGES_PER_CONVERSATION + 2)] : conv.messages.slice(-MAX_MESSAGES_PER_CONVERSATION + 1);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
conv.messages.push({ role, content, timestamp: now });
|
|
181
|
+
conv.updatedAt = now;
|
|
182
|
+
return conv;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const existing = db.exec('SELECT id FROM conversations WHERE id = ?', [conversationId]);
|
|
186
|
+
if (!existing.length || !existing[0].values.length) {
|
|
187
|
+
createConversation(conversationId);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const countResult = db.exec('SELECT COUNT(*) as count FROM messages WHERE conversation_id = ?', [conversationId]);
|
|
191
|
+
const count = countResult.length ? countResult[0].values[0][0] : 0;
|
|
192
|
+
|
|
193
|
+
if (count >= MAX_MESSAGES_PER_CONVERSATION) {
|
|
194
|
+
const systemMsgResult = db.exec("SELECT id FROM messages WHERE conversation_id = ? AND role = 'system' LIMIT 1", [conversationId]);
|
|
195
|
+
const deleteCount = count - MAX_MESSAGES_PER_CONVERSATION + 2;
|
|
196
|
+
|
|
197
|
+
if (systemMsgResult.length && systemMsgResult[0].values.length) {
|
|
198
|
+
const systemId = systemMsgResult[0].values[0][0];
|
|
199
|
+
// Delete oldest messages except system message
|
|
200
|
+
const toDeleteResult = db.exec('SELECT id FROM messages WHERE conversation_id = ? AND id != ? ORDER BY id LIMIT ?',
|
|
201
|
+
[conversationId, systemId, deleteCount]);
|
|
202
|
+
if (toDeleteResult.length) {
|
|
203
|
+
toDeleteResult[0].values.forEach(row => {
|
|
204
|
+
db.run('DELETE FROM messages WHERE id = ?', [row[0]]);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
const toDeleteResult = db.exec('SELECT id FROM messages WHERE conversation_id = ? ORDER BY id LIMIT ?',
|
|
209
|
+
[conversationId, deleteCount]);
|
|
210
|
+
if (toDeleteResult.length) {
|
|
211
|
+
toDeleteResult[0].values.forEach(row => {
|
|
212
|
+
db.run('DELETE FROM messages WHERE id = ?', [row[0]]);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
db.run('INSERT INTO messages (conversation_id, role, content, timestamp) VALUES (?, ?, ?, ?)',
|
|
219
|
+
[conversationId, role, content, now]);
|
|
220
|
+
|
|
221
|
+
db.run('UPDATE conversations SET updated_at = ? WHERE id = ?', [now, conversationId]);
|
|
222
|
+
scheduleSave();
|
|
223
|
+
|
|
224
|
+
return getConversation(conversationId);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
export function listAllConversations() {
|
|
229
|
+
if (STORE_TYPE === 'memory' || !db) {
|
|
230
|
+
const conversations = [];
|
|
231
|
+
for (const [id, conv] of memoryStore.entries()) {
|
|
232
|
+
conversations.push({
|
|
233
|
+
id,
|
|
234
|
+
message_count: conv.messages.length,
|
|
235
|
+
created_at: new Date(conv.createdAt).toISOString(),
|
|
236
|
+
updated_at: new Date(conv.updatedAt).toISOString(),
|
|
237
|
+
metadata: conv.metadata,
|
|
238
|
+
preview: conv.messages.slice(-1)[0]?.content?.substring(0, 100) || ''
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
return conversations.sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const convsResult = db.exec('SELECT * FROM conversations ORDER BY updated_at DESC');
|
|
245
|
+
if (!convsResult.length) return [];
|
|
246
|
+
|
|
247
|
+
return convsResult[0].values.map(conv => {
|
|
248
|
+
const [id, metadata, created_at, updated_at] = conv;
|
|
249
|
+
const lastMsgResult = db.exec('SELECT content FROM messages WHERE conversation_id = ? ORDER BY id DESC LIMIT 1', [id]);
|
|
250
|
+
const msgCountResult = db.exec('SELECT COUNT(*) as count FROM messages WHERE conversation_id = ?', [id]);
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
id,
|
|
254
|
+
message_count: msgCountResult.length ? msgCountResult[0].values[0][0] : 0,
|
|
255
|
+
created_at: new Date(created_at).toISOString(),
|
|
256
|
+
updated_at: new Date(updated_at).toISOString(),
|
|
257
|
+
metadata: JSON.parse(metadata || '{}'),
|
|
258
|
+
preview: lastMsgResult.length && lastMsgResult[0].values.length ? lastMsgResult[0].values[0][0]?.substring(0, 100) || '' : ''
|
|
259
|
+
};
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function deleteConversation(conversationId) {
|
|
264
|
+
if (STORE_TYPE === 'memory' || !db) {
|
|
265
|
+
return memoryStore.delete(conversationId);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
db.run('DELETE FROM messages WHERE conversation_id = ?', [conversationId]);
|
|
269
|
+
db.run('DELETE FROM conversations WHERE id = ?', [conversationId]);
|
|
270
|
+
scheduleSave();
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function clearAllConversations() {
|
|
275
|
+
if (STORE_TYPE === 'memory' || !db) {
|
|
276
|
+
const count = memoryStore.size;
|
|
277
|
+
memoryStore.clear();
|
|
278
|
+
return count;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const countResult = db.exec('SELECT COUNT(*) as count FROM conversations');
|
|
282
|
+
const count = countResult.length ? countResult[0].values[0][0] : 0;
|
|
283
|
+
db.run('DELETE FROM messages');
|
|
284
|
+
db.run('DELETE FROM conversations');
|
|
285
|
+
scheduleSave();
|
|
286
|
+
return count;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function getConversationCount() {
|
|
290
|
+
if (STORE_TYPE === 'memory' || !db) {
|
|
291
|
+
return memoryStore.size;
|
|
292
|
+
}
|
|
293
|
+
const result = db.exec('SELECT COUNT(*) as count FROM conversations');
|
|
294
|
+
return result.length ? result[0].values[0][0] : 0;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function cleanupConversations() {
|
|
298
|
+
const now = Date.now();
|
|
299
|
+
const expireTime = now - CONVERSATION_TTL;
|
|
300
|
+
|
|
301
|
+
if (STORE_TYPE === 'memory' || !db) {
|
|
302
|
+
let deleted = 0;
|
|
303
|
+
for (const [id, conv] of memoryStore.entries()) {
|
|
304
|
+
if (conv.updatedAt < expireTime) {
|
|
305
|
+
memoryStore.delete(id);
|
|
306
|
+
deleted++;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (memoryStore.size > MAX_CONVERSATIONS) {
|
|
311
|
+
const sorted = [...memoryStore.entries()].sort((a, b) => a[1].updatedAt - b[1].updatedAt);
|
|
312
|
+
const toDelete = sorted.slice(0, memoryStore.size - MAX_CONVERSATIONS);
|
|
313
|
+
toDelete.forEach(([id]) => memoryStore.delete(id));
|
|
314
|
+
deleted += toDelete.length;
|
|
315
|
+
}
|
|
316
|
+
return deleted;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Delete expired conversations
|
|
320
|
+
const expiredResult = db.exec('SELECT id FROM conversations WHERE updated_at < ?', [expireTime]);
|
|
321
|
+
let deleted = 0;
|
|
322
|
+
if (expiredResult.length) {
|
|
323
|
+
expiredResult[0].values.forEach(row => {
|
|
324
|
+
db.run('DELETE FROM messages WHERE conversation_id = ?', [row[0]]);
|
|
325
|
+
db.run('DELETE FROM conversations WHERE id = ?', [row[0]]);
|
|
326
|
+
deleted++;
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Enforce max conversations limit
|
|
331
|
+
const countResult = db.exec('SELECT COUNT(*) as count FROM conversations');
|
|
332
|
+
const count = countResult.length ? countResult[0].values[0][0] : 0;
|
|
333
|
+
if (count > MAX_CONVERSATIONS) {
|
|
334
|
+
const toDeleteResult = db.exec('SELECT id FROM conversations ORDER BY updated_at ASC LIMIT ?', [count - MAX_CONVERSATIONS]);
|
|
335
|
+
if (toDeleteResult.length) {
|
|
336
|
+
toDeleteResult[0].values.forEach(row => {
|
|
337
|
+
db.run('DELETE FROM messages WHERE conversation_id = ?', [row[0]]);
|
|
338
|
+
db.run('DELETE FROM conversations WHERE id = ?', [row[0]]);
|
|
339
|
+
deleted++;
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (deleted > 0) scheduleSave();
|
|
345
|
+
return deleted;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Run cleanup every hour
|
|
349
|
+
setInterval(cleanupConversations, 60 * 60 * 1000);
|
|
350
|
+
|
|
351
|
+
// Export config for health check
|
|
352
|
+
export const storeConfig = {
|
|
353
|
+
STORE_TYPE,
|
|
354
|
+
DB_PATH,
|
|
355
|
+
MAX_CONVERSATIONS,
|
|
356
|
+
CONVERSATION_TTL,
|
|
357
|
+
isDbConnected: () => !!db
|
|
358
|
+
};
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Parameter Mapper Module
|
|
3
|
+
* Maps OpenAI API parameters to You.com API parameters
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
getAdvancedVersion,
|
|
8
|
+
isAdvancedVersion,
|
|
9
|
+
getDefaultAdvancedVersion,
|
|
10
|
+
adjustWorkflowSteps
|
|
11
|
+
} from './advanced-versions.js';
|
|
12
|
+
|
|
13
|
+
// Parse custom agents from env
|
|
14
|
+
function getCustomAgents() {
|
|
15
|
+
const raw = process.env.YDC_CUSTOM_AGENTS || '';
|
|
16
|
+
if (!raw) return new Map();
|
|
17
|
+
|
|
18
|
+
const map = new Map();
|
|
19
|
+
raw.split(',').forEach(entry => {
|
|
20
|
+
const trimmed = entry.trim();
|
|
21
|
+
if (!trimmed) return;
|
|
22
|
+
|
|
23
|
+
const colonIndex = trimmed.indexOf(':');
|
|
24
|
+
if (colonIndex > 0) {
|
|
25
|
+
map.set(trimmed.substring(0, colonIndex), trimmed.substring(colonIndex + 1));
|
|
26
|
+
} else {
|
|
27
|
+
map.set(trimmed, trimmed);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
return map;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Map OpenAI request parameters to You.com parameters
|
|
35
|
+
*/
|
|
36
|
+
export function mapOpenAIToYouParams(openaiRequest) {
|
|
37
|
+
const {
|
|
38
|
+
model = 'advanced-3.0-high',
|
|
39
|
+
messages,
|
|
40
|
+
temperature = 0.7,
|
|
41
|
+
max_tokens = 1000,
|
|
42
|
+
stream = false,
|
|
43
|
+
tools = []
|
|
44
|
+
} = openaiRequest;
|
|
45
|
+
|
|
46
|
+
let input = '';
|
|
47
|
+
let systemPrompt = '';
|
|
48
|
+
const conversationHistory = [];
|
|
49
|
+
|
|
50
|
+
messages.forEach(msg => {
|
|
51
|
+
if (msg.role === 'system') {
|
|
52
|
+
systemPrompt = msg.content;
|
|
53
|
+
} else if (msg.role === 'user') {
|
|
54
|
+
conversationHistory.push(`User: ${msg.content}`);
|
|
55
|
+
} else if (msg.role === 'assistant') {
|
|
56
|
+
conversationHistory.push(`Assistant: ${msg.content}`);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (systemPrompt) {
|
|
61
|
+
input = `[System Instructions]\n${systemPrompt}\n\n`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (conversationHistory.length > 1) {
|
|
65
|
+
input += `[Conversation History]\n${conversationHistory.slice(0, -1).join('\n\n')}\n\n`;
|
|
66
|
+
input += `[Current Message]\n${conversationHistory[conversationHistory.length - 1].replace(/^User: /, '')}`;
|
|
67
|
+
} else if (conversationHistory.length === 1) {
|
|
68
|
+
input += conversationHistory[0].replace(/^User: /, '');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Check if it's an advanced version model
|
|
72
|
+
if (isAdvancedVersion(model)) {
|
|
73
|
+
const versionConfig = getAdvancedVersion(model);
|
|
74
|
+
if (versionConfig) {
|
|
75
|
+
const adjustedSteps = adjustWorkflowSteps(versionConfig.max_workflow_steps, temperature);
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
agent: 'advanced',
|
|
79
|
+
input,
|
|
80
|
+
stream,
|
|
81
|
+
verbosity: versionConfig.verbosity,
|
|
82
|
+
tools: tools.length > 0 ? tools : versionConfig.tools,
|
|
83
|
+
workflow_config: {
|
|
84
|
+
max_workflow_steps: adjustedSteps
|
|
85
|
+
},
|
|
86
|
+
timeout: versionConfig.timeout
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Handle legacy models (express, research, advanced)
|
|
92
|
+
let agent = model;
|
|
93
|
+
let verbosity = 'medium';
|
|
94
|
+
let defaultTools = tools;
|
|
95
|
+
let timeout = 300000;
|
|
96
|
+
|
|
97
|
+
if (model === 'advanced') {
|
|
98
|
+
const defaultVersion = getDefaultAdvancedVersion(temperature);
|
|
99
|
+
const versionConfig = getAdvancedVersion(defaultVersion);
|
|
100
|
+
if (versionConfig) {
|
|
101
|
+
const adjustedSteps = adjustWorkflowSteps(versionConfig.max_workflow_steps, temperature);
|
|
102
|
+
return {
|
|
103
|
+
agent: 'advanced',
|
|
104
|
+
input,
|
|
105
|
+
stream,
|
|
106
|
+
verbosity: versionConfig.verbosity,
|
|
107
|
+
tools: tools.length > 0 ? tools : versionConfig.tools,
|
|
108
|
+
workflow_config: {
|
|
109
|
+
max_workflow_steps: adjustedSteps
|
|
110
|
+
},
|
|
111
|
+
timeout: versionConfig.timeout
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (temperature <= 0.3) verbosity = 'medium';
|
|
117
|
+
else if (temperature >= 0.8) verbosity = 'high';
|
|
118
|
+
|
|
119
|
+
const max_workflow_steps = Math.min(Math.max(Math.floor(max_tokens / 100), 1), 20);
|
|
120
|
+
|
|
121
|
+
// Check if model is a known legacy type
|
|
122
|
+
const knownAgents = ['express', 'research', 'advanced'];
|
|
123
|
+
|
|
124
|
+
// Check custom agents mapping
|
|
125
|
+
const customAgents = getCustomAgents();
|
|
126
|
+
if (customAgents.has(model)) {
|
|
127
|
+
return {
|
|
128
|
+
agent: customAgents.get(model),
|
|
129
|
+
input,
|
|
130
|
+
stream,
|
|
131
|
+
timeout: 300000
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!knownAgents.includes(agent)) {
|
|
136
|
+
// Treat unknown model as custom agent ID
|
|
137
|
+
return {
|
|
138
|
+
agent: model,
|
|
139
|
+
input,
|
|
140
|
+
stream,
|
|
141
|
+
timeout: 300000
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (agent === 'advanced') {
|
|
146
|
+
timeout = 3000000;
|
|
147
|
+
if (tools.length === 0) {
|
|
148
|
+
defaultTools = [
|
|
149
|
+
{ type: 'research', search_effort: 'auto', report_verbosity: 'medium' },
|
|
150
|
+
{ type: 'compute' }
|
|
151
|
+
];
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
agent,
|
|
157
|
+
input,
|
|
158
|
+
stream,
|
|
159
|
+
verbosity,
|
|
160
|
+
tools: defaultTools,
|
|
161
|
+
workflow_config: {
|
|
162
|
+
max_workflow_steps
|
|
163
|
+
},
|
|
164
|
+
timeout
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Convert You.com response to OpenAI format
|
|
170
|
+
*/
|
|
171
|
+
export function convertToOpenAIResponse(youResponse, model) {
|
|
172
|
+
const content = youResponse.output && Array.isArray(youResponse.output)
|
|
173
|
+
? youResponse.output
|
|
174
|
+
.filter(item => item.type === 'message.answer')
|
|
175
|
+
.map(item => item.text)
|
|
176
|
+
.join('\n\n')
|
|
177
|
+
: 'No response content';
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
id: `chatcmpl-${Date.now()}`,
|
|
181
|
+
object: 'chat.completion',
|
|
182
|
+
created: Math.floor(Date.now() / 1000),
|
|
183
|
+
model: `you-${model}`,
|
|
184
|
+
choices: [{
|
|
185
|
+
index: 0,
|
|
186
|
+
message: {
|
|
187
|
+
role: 'assistant',
|
|
188
|
+
content: content
|
|
189
|
+
},
|
|
190
|
+
finish_reason: 'stop'
|
|
191
|
+
}],
|
|
192
|
+
usage: {
|
|
193
|
+
prompt_tokens: Math.floor(Math.random() * 100) + 50,
|
|
194
|
+
completion_tokens: Math.floor(content.length / 4),
|
|
195
|
+
total_tokens: Math.floor(Math.random() * 100) + 50 + Math.floor(content.length / 4)
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Create streaming chunk in OpenAI format
|
|
202
|
+
*/
|
|
203
|
+
export function createStreamChunk(model, content, finishReason = null) {
|
|
204
|
+
return {
|
|
205
|
+
id: `chatcmpl-${Date.now()}`,
|
|
206
|
+
object: 'chat.completion.chunk',
|
|
207
|
+
created: Math.floor(Date.now() / 1000),
|
|
208
|
+
model: `you-${model}`,
|
|
209
|
+
choices: [{
|
|
210
|
+
index: 0,
|
|
211
|
+
delta: content ? { content } : {},
|
|
212
|
+
finish_reason: finishReason
|
|
213
|
+
}]
|
|
214
|
+
};
|
|
215
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request Logger Module
|
|
3
|
+
* Pretty prints request/response info using cli-table3
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
let Table;
|
|
7
|
+
let tableAvailable = false;
|
|
8
|
+
|
|
9
|
+
// Try to load cli-table3 (optional dependency)
|
|
10
|
+
try {
|
|
11
|
+
Table = (await import('cli-table3')).default;
|
|
12
|
+
tableAvailable = true;
|
|
13
|
+
} catch (e) {
|
|
14
|
+
// cli-table3 not available, use simple logging
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Track last request ID for comparison
|
|
18
|
+
let lastRequestId = null;
|
|
19
|
+
|
|
20
|
+
// Check if no-history mode
|
|
21
|
+
const noHistory = process.env.YDC_NO_HISTORY === 'true';
|
|
22
|
+
|
|
23
|
+
// Helper to create table
|
|
24
|
+
function createTable() {
|
|
25
|
+
return new Table({
|
|
26
|
+
colWidths: [4, 12, 60],
|
|
27
|
+
wordWrap: true,
|
|
28
|
+
chars: {
|
|
29
|
+
'top': 'β', 'top-mid': 'β¬', 'top-left': 'β', 'top-right': 'β',
|
|
30
|
+
'bottom': 'β', 'bottom-mid': 'β΄', 'bottom-left': 'β', 'bottom-right': 'β',
|
|
31
|
+
'left': 'β', 'left-mid': 'β', 'mid': 'β', 'mid-mid': 'βΌ',
|
|
32
|
+
'right': 'β', 'right-mid': 'β€', 'middle': 'β'
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Helper to print history
|
|
38
|
+
function printHistory(history, label) {
|
|
39
|
+
if (noHistory || history.length === 0) return;
|
|
40
|
+
|
|
41
|
+
if (tableAvailable) {
|
|
42
|
+
const table = createTable();
|
|
43
|
+
history.forEach((item, index) => {
|
|
44
|
+
const content = item.content || '';
|
|
45
|
+
const preview = content.length > 80 ? content.substring(0, 80) + '...' : content;
|
|
46
|
+
table.push([index + 1, item.role, preview]);
|
|
47
|
+
});
|
|
48
|
+
console.log(` ${label}:`);
|
|
49
|
+
console.log(table.toString());
|
|
50
|
+
} else {
|
|
51
|
+
console.log(` ${label}:`);
|
|
52
|
+
history.forEach((item, index) => {
|
|
53
|
+
const content = item.content || '';
|
|
54
|
+
const preview = content.length > 50 ? content.substring(0, 50) + '...' : content;
|
|
55
|
+
console.log(` ${index + 1}. [${item.role}] ${preview}`);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Log request with history
|
|
62
|
+
*/
|
|
63
|
+
export function logRequest(info) {
|
|
64
|
+
const {
|
|
65
|
+
conversationId,
|
|
66
|
+
agent = 'unknown',
|
|
67
|
+
stream = false,
|
|
68
|
+
messageCount = 0,
|
|
69
|
+
inputMessages = []
|
|
70
|
+
} = info;
|
|
71
|
+
|
|
72
|
+
const convId = conversationId || 'new';
|
|
73
|
+
lastRequestId = convId;
|
|
74
|
+
const streamMode = stream ? 'stream' : 'sync';
|
|
75
|
+
|
|
76
|
+
if (noHistory) {
|
|
77
|
+
console.log(`π€ ${convId} | ${agent}(${streamMode}) | msgs:${messageCount}`);
|
|
78
|
+
} else {
|
|
79
|
+
console.log(`π€ Request: ${convId}, Messages: ${messageCount}`);
|
|
80
|
+
console.log(` ${agent}(${streamMode})`);
|
|
81
|
+
printHistory(inputMessages, 'History');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Log stream complete with history
|
|
87
|
+
*/
|
|
88
|
+
export function logStreamComplete(info) {
|
|
89
|
+
const {
|
|
90
|
+
conversationId,
|
|
91
|
+
contentLength = 0,
|
|
92
|
+
messageCount = 0,
|
|
93
|
+
agent = 'unknown',
|
|
94
|
+
stream = true,
|
|
95
|
+
inputMessages = []
|
|
96
|
+
} = info;
|
|
97
|
+
|
|
98
|
+
const streamMode = stream ? 'stream' : 'sync';
|
|
99
|
+
const convId = conversationId || 'new';
|
|
100
|
+
|
|
101
|
+
// ε¦ζ Complete ID ε Request ID δΈεοΌι‘―η€Ίζ¬θ
|
|
102
|
+
let idDisplay;
|
|
103
|
+
if (lastRequestId && lastRequestId !== convId) {
|
|
104
|
+
idDisplay = `(${convId})`;
|
|
105
|
+
} else {
|
|
106
|
+
idDisplay = convId;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (noHistory) {
|
|
110
|
+
console.log(`π₯ ${idDisplay} | ${agent}(${streamMode}) | ${contentLength}chars | msgs:${messageCount}`);
|
|
111
|
+
} else {
|
|
112
|
+
console.log(`π₯ Complete: ${idDisplay}, ${contentLength} chars, Messages: ${messageCount}`);
|
|
113
|
+
console.log(` ${agent}(${streamMode})`);
|
|
114
|
+
printHistory(inputMessages, 'History');
|
|
115
|
+
console.log('');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Log error
|
|
121
|
+
*/
|
|
122
|
+
export function logError(conversationId, error) {
|
|
123
|
+
const shortId = conversationId ? conversationId.split('-')[0] : 'unknown';
|
|
124
|
+
console.log(`β ${shortId} | ${error.message || error}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Log response (for non-streaming)
|
|
129
|
+
*/
|
|
130
|
+
export function logResponse(info) {
|
|
131
|
+
logStreamComplete(info);
|
|
132
|
+
}
|