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.
@@ -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
+ }