Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/apizr00_jarvis.json
T

1 line
88 KiB
JSON

{"repo": "apizr00/jarvis", "files": {"src/llm/deepseek.js": {"8f81e9212d9f3e39a3785401657d4cc5ca34b7f5": {"t": 1785746676.6643915, "n": 6247, "h": "22bb812a47830ebf", "c": "// src/llm/deepseek.js\n// DeepSeek API provider\nrequire('dotenv').config();\nconst axios = require('axios');\nconst db = require('../db');\nconst redisCache = require('../redis');\nconst memory = require('../memory');\nconst relationships = require('../memory/relationships');\nconst { buildSystemPrompt, parseAndValidate } = require('./shared');\n\nconst DEEPSEEK_URL = (process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com') + '/v1/chat/completions';\n\nasync function chat(userId, userMessage, conversationHistory, options = {}, prefetched = null) {\n if (!conversationHistory) conversationHistory = [];\n\n const executiveContext = options.executiveContext || '';\n\n // Reuse pre-fetched context from llm/index.js, or fetch directly.\n // All tiers now get the same data — no more minimal/skipping.\n let facts, upcomingReminders, peopleContext;\n if (prefetched) {\n ({ facts, upcomingReminders, peopleContext } = prefetched);\n } else {\n [facts, upcomingReminders, peopleContext] = await Promise.all([\n memory.searchFacts(userId, userMessage),\n db.getUpcomingReminders(userId, 15),\n relationships.getPeopleContext(userId, userMessage, 5),\n ]);\n memory.recordFactAccess(userId, facts.map(f => f.key));\n }\n\n const systemPrompt = await buildSystemPrompt(\n userId, facts, process.env.TIMEZONE || 'UTC', upcomingReminders, peopleContext,\n { tier: options.tier, executiveContext }\n );\n\n const messages = [\n { role: 'system', content: systemPrompt }\n ].concat(conversationHistory).concat([\n { role: 'user', content: userMessage }\n ]);\n\n const response = await axios.post(\n DEEPSEEK_URL,\n {\n model: 'deepseek-chat',\n messages: messages,\n max_tokens: options.maxTokens || 800,\n temperature: 0.3,\n },\n {\n headers: {\n 'Authorization': 'Bearer ' + process.env.DEEPSEEK_API_KEY,\n 'Content-Type': 'application/json',\n },\n timeout: 15000,\n }\n );\n\n const rawText = response.data.choices[0].message.content.trim();\n console.log('[DeepSeek] Raw response:', rawText.slice(0, 300));\n\n return parseAndValidate(rawText, {\n upcomingReminders,\n userMessage,\n facts,\n });\n}\n\n/**\n * Streaming version — calls onChunk(displayText) progressively as tokens arrive.\n * Only extracts displayable content for message-type responses.\n * For tool calls, buffers silently and returns the parsed result.\n *\n * @param {string} userId\n * @param {string} userMessage\n * @param {Array} conversationHistory\n * @param {object} options\n * @param {object|null} prefetched\n * @param {function} onChunk - callback(displayText) for each accumulation\n * @returns {Promise<{type:string, content?:string, name?:string, args?:object}>}\n */\nasync function chatStream(userId, userMessage, conversationHistory, options = {}, prefetched = null, onChunk) {\n if (!conversationHistory) conversationHistory = [];\n if (typeof onChunk !== 'function') onChunk = () => { };\n\n const executiveContext = options.executiveContext || '';\n\n let facts, upcomingReminders, peopleContext;\n if (prefetched) {\n ({ facts, upcomingReminders, peopleContext } = prefetched);\n } else {\n [facts, upcomingReminders, peopleContext] = await Promise.all([\n memory.searchFacts(userId, userMessage),\n db.getUpcomingReminders(userId, 15),\n relationships.getPeopleContext(userId, userMessage, 5),\n ]);\n memory.recordFactAccess(userId, facts.map(f => f.key));\n }\n\n const systemPrompt = await buildSystemPrompt(\n userId, facts, process.env.TIMEZONE || 'UTC', upcomingReminders, peopleContext,\n { tier: options.tier, executiveContext }\n );\n\n const messages = [\n { role: 'system', content: systemPrompt }\n ].concat(conversationHistory).concat([\n { role: 'user', content: userMessage }\n ]);\n\n // ── Streaming request to DeepSeek ──────────────────────────────────────\n const response = await axios.post(\n DEEPSEEK_URL,\n {\n model: 'deepseek-chat',\n messages: messages,\n max_tokens: options.maxTokens || 800,\n temperature: 0.3,\n stream: true,\n },\n {\n headers: {\n 'Authorization': 'Bearer ' + process.env.DEEPSEEK_API_KEY,\n 'Content-Type': 'application/json',\n },\n timeout: 30000,\n responseType: 'stream',\n }\n );\n\n // ── Parse SSE stream ───────────────────────────────────────────────────\n let rawText = '';\n let isMessage = null; // null=unknown, true=message, false=tool\n const MESSAGE_PREFIX = '{\"type\":\"message\",\"content\":\"';\n\n await new Promise((resolve, reject) => {\n let buffer = '';\n\n response.data.on('data', (chunk) => {\n buffer += chunk.toString();\n\n // Process complete SSE lines\n const lines = buffer.split('\\n');\n buffer = lines.pop() || ''; // keep incomplete line in buffer\n\n for (const line of lines) {\n if (!line.startsWith('data: ') || line === 'data: [DONE]') continue;\n\n try {\n const json = JSON.parse(line.slice(6)); // strip \"data: \" prefix\n const delta = json.choices?.[0]?.delta?.content;\n if (!delta) continue;\n\n rawText += delta;\n\n // Detect message vs tool on first meaningful tokens\n if (isMessage === null && rawText.length > 10) {\n isMessage = rawText.trimStart().startsWith(MESSAGE_PREFIX);\n }\n\n // 🔥 Show streaming text for message-type responses\n if (isMessage === true && onChunk) {\n let display = rawText;\n // Strip JSON wrapper to show just the content\n display = display.replace(/^\\s*\\{\"type\":\"message\",\"content\":\"/, '');\n // Don't strip trailing quotes until complete (they may be partial)\n if (display.endsWith('\"}') && rawText.endsWith('\"}')) {\n display = display.slice(0, -2);\n }\n onChunk(display);\n }\n } catch {\n // Skip malformed SSE chunks\n }\n }\n });\n\n response.data.on('end', resolve);\n response.data.on('error', reject);\n });\n\n console.log('[DeepSeek] Stream complete (' + rawText.length + ' chars):', rawText.slice(0, 200));\n\n return parseAndValidate(rawText, {\n upcomingReminders,\n userMessage,\n facts,\n });\n}\n\nmodule.exports = { chat, chatStream };\n"}, "742ce7f2e7fbfdf212d986cd160d171e63ee4f08": {"t": 1785766753.2542262, "n": 6247, "h": "22bb812a47830ebf", "c": "// src/llm/deepseek.js\n// DeepSeek API provider\nrequire('dotenv').config();\nconst axios = require('axios');\nconst db = require('../db');\nconst redisCache = require('../redis');\nconst memory = require('../memory');\nconst relationships = require('../memory/relationships');\nconst { buildSystemPrompt, parseAndValidate } = require('./shared');\n\nconst DEEPSEEK_URL = (process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com') + '/v1/chat/completions';\n\nasync function chat(userId, userMessage, conversationHistory, options = {}, prefetched = null) {\n if (!conversationHistory) conversationHistory = [];\n\n const executiveContext = options.executiveContext || '';\n\n // Reuse pre-fetched context from llm/index.js, or fetch directly.\n // All tiers now get the same data — no more minimal/skipping.\n let facts, upcomingReminders, peopleContext;\n if (prefetched) {\n ({ facts, upcomingReminders, peopleContext } = prefetched);\n } else {\n [facts, upcomingReminders, peopleContext] = await Promise.all([\n memory.searchFacts(userId, userMessage),\n db.getUpcomingReminders(userId, 15),\n relationships.getPeopleContext(userId, userMessage, 5),\n ]);\n memory.recordFactAccess(userId, facts.map(f => f.key));\n }\n\n const systemPrompt = await buildSystemPrompt(\n userId, facts, process.env.TIMEZONE || 'UTC', upcomingReminders, peopleContext,\n { tier: options.tier, executiveContext }\n );\n\n const messages = [\n { role: 'system', content: systemPrompt }\n ].concat(conversationHistory).concat([\n { role: 'user', content: userMessage }\n ]);\n\n const response = await axios.post(\n DEEPSEEK_URL,\n {\n model: 'deepseek-chat',\n messages: messages,\n max_tokens: options.maxTokens || 800,\n temperature: 0.3,\n },\n {\n headers: {\n 'Authorization': 'Bearer ' + process.env.DEEPSEEK_API_KEY,\n 'Content-Type': 'application/json',\n },\n timeout: 15000,\n }\n );\n\n const rawText = response.data.choices[0].message.content.trim();\n console.log('[DeepSeek] Raw response:', rawText.slice(0, 300));\n\n return parseAndValidate(rawText, {\n upcomingReminders,\n userMessage,\n facts,\n });\n}\n\n/**\n * Streaming version — calls onChunk(displayText) progressively as tokens arrive.\n * Only extracts displayable content for message-type responses.\n * For tool calls, buffers silently and returns the parsed result.\n *\n * @param {string} userId\n * @param {string} userMessage\n * @param {Array} conversationHistory\n * @param {object} options\n * @param {object|null} prefetched\n * @param {function} onChunk - callback(displayText) for each accumulation\n * @returns {Promise<{type:string, content?:string, name?:string, args?:object}>}\n */\nasync function chatStream(userId, userMessage, conversationHistory, options = {}, prefetched = null, onChunk) {\n if (!conversationHistory) conversationHistory = [];\n if (typeof onChunk !== 'function') onChunk = () => { };\n\n const executiveContext = options.executiveContext || '';\n\n let facts, upcomingReminders, peopleContext;\n if (prefetched) {\n ({ facts, upcomingReminders, peopleContext } = prefetched);\n } else {\n [facts, upcomingReminders, peopleContext] = await Promise.all([\n memory.searchFacts(userId, userMessage),\n db.getUpcomingReminders(userId, 15),\n relationships.getPeopleContext(userId, userMessage, 5),\n ]);\n memory.recordFactAccess(userId, facts.map(f => f.key));\n }\n\n const systemPrompt = await buildSystemPrompt(\n userId, facts, process.env.TIMEZONE || 'UTC', upcomingReminders, peopleContext,\n { tier: options.tier, executiveContext }\n );\n\n const messages = [\n { role: 'system', content: systemPrompt }\n ].concat(conversationHistory).concat([\n { role: 'user', content: userMessage }\n ]);\n\n // ── Streaming request to DeepSeek ──────────────────────────────────────\n const response = await axios.post(\n DEEPSEEK_URL,\n {\n model: 'deepseek-chat',\n messages: messages,\n max_tokens: options.maxTokens || 800,\n temperature: 0.3,\n stream: true,\n },\n {\n headers: {\n 'Authorization': 'Bearer ' + process.env.DEEPSEEK_API_KEY,\n 'Content-Type': 'application/json',\n },\n timeout: 30000,\n responseType: 'stream',\n }\n );\n\n // ── Parse SSE stream ───────────────────────────────────────────────────\n let rawText = '';\n let isMessage = null; // null=unknown, true=message, false=tool\n const MESSAGE_PREFIX = '{\"type\":\"message\",\"content\":\"';\n\n await new Promise((resolve, reject) => {\n let buffer = '';\n\n response.data.on('data', (chunk) => {\n buffer += chunk.toString();\n\n // Process complete SSE lines\n const lines = buffer.split('\\n');\n buffer = lines.pop() || ''; // keep incomplete line in buffer\n\n for (const line of lines) {\n if (!line.startsWith('data: ') || line === 'data: [DONE]') continue;\n\n try {\n const json = JSON.parse(line.slice(6)); // strip \"data: \" prefix\n const delta = json.choices?.[0]?.delta?.content;\n if (!delta) continue;\n\n rawText += delta;\n\n // Detect message vs tool on first meaningful tokens\n if (isMessage === null && rawText.length > 10) {\n isMessage = rawText.trimStart().startsWith(MESSAGE_PREFIX);\n }\n\n // 🔥 Show streaming text for message-type responses\n if (isMessage === true && onChunk) {\n let display = rawText;\n // Strip JSON wrapper to show just the content\n display = display.replace(/^\\s*\\{\"type\":\"message\",\"content\":\"/, '');\n // Don't strip trailing quotes until complete (they may be partial)\n if (display.endsWith('\"}') && rawText.endsWith('\"}')) {\n display = display.slice(0, -2);\n }\n onChunk(display);\n }\n } catch {\n // Skip malformed SSE chunks\n }\n }\n });\n\n response.data.on('end', resolve);\n response.data.on('error', reject);\n });\n\n console.log('[DeepSeek] Stream complete (' + rawText.length + ' chars):', rawText.slice(0, 200));\n\n return parseAndValidate(rawText, {\n upcomingReminders,\n userMessage,\n facts,\n });\n}\n\nmodule.exports = { chat, chatStream };\n"}, "66d5264e2d9885a4e5cd8c3a3d2ab69f0c110600": {"t": 1785766754.7506547, "n": 6462, "h": "faaed455178443b8", "c": "// src/llm/deepseek.js\n// DeepSeek API provider\nrequire('dotenv').config();\nconst axios = require('axios');\nconst db = require('../db');\nconst redisCache = require('../redis');\nconst memory = require('../memory');\nconst relationships = require('../memory/relationships');\nconst { buildSystemPrompt, parseAndValidate } = require('./shared');\n\nconst DEEPSEEK_URL = (process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com') + '/v1/chat/completions';\n\nasync function chat(userId, userMessage, conversationHistory, options = {}, prefetched = null) {\n if (!conversationHistory) conversationHistory = [];\n\n const minimal = options.minimal === true;\n const executiveContext = options.executiveContext || '';\n\n // Reuse pre-fetched context from llm/index.js, or fetch in parallel\n let facts, upcomingReminders, peopleContext;\n if (prefetched) {\n ({ facts, upcomingReminders, peopleContext } = prefetched);\n } else if (!minimal) {\n [facts, upcomingReminders, peopleContext] = await Promise.all([\n memory.searchFacts(userId, userMessage),\n db.getUpcomingReminders(userId, 15),\n relationships.getPeopleContext(userId, userMessage, 5),\n ]);\n memory.recordFactAccess(userId, facts.map(f => f.key));\n } else {\n facts = [];\n upcomingReminders = [];\n peopleContext = '';\n }\n\n const systemPrompt = await buildSystemPrompt(\n userId, facts, process.env.TIMEZONE || 'UTC', upcomingReminders, peopleContext,\n { minimal, executiveContext }\n );\n\n const messages = [\n { role: 'system', content: systemPrompt }\n ].concat(conversationHistory).concat([\n { role: 'user', content: userMessage }\n ]);\n\n const response = await axios.post(\n DEEPSEEK_URL,\n {\n model: 'deepseek-chat',\n messages: messages,\n max_tokens: options.maxTokens || 800,\n temperature: 0.3,\n },\n {\n headers: {\n 'Authorization': 'Bearer ' + process.env.DEEPSEEK_API_KEY,\n 'Content-Type': 'application/json',\n },\n timeout: 15000,\n }\n );\n\n const rawText = response.data.choices[0].message.content.trim();\n console.log('[DeepSeek] Raw response:', rawText.slice(0, 300));\n\n return parseAndValidate(rawText, {\n minimal,\n upcomingReminders,\n userMessage,\n facts,\n });\n}\n\n/**\n * Streaming version — calls onChunk(displayText) progressively as tokens arrive.\n * Only extracts displayable content for message-type responses.\n * For tool calls, buffers silently and returns the parsed result.\n *\n * @param {string} userId\n * @param {string} userMessage\n * @param {Array} conversationHistory\n * @param {object} options\n * @param {object|null} prefetched\n * @param {function} onChunk - callback(displayText) for each accumulation\n * @returns {Promise<{type:string, content?:string, name?:string, args?:object}>}\n */\nasync function chatStream(userId, userMessage, conversationHistory, options = {}, prefetched = null, onChunk) {\n if (!conversationHistory) conversationHistory = [];\n if (typeof onChunk !== 'function') onChunk = () => { };\n\n const minimal = options.minimal === true;\n const executiveContext = options.executiveContext || '';\n\n let facts, upcomingReminders, peopleContext;\n if (prefetched) {\n ({ facts, upcomingReminders, peopleContext } = prefetched);\n } else if (!minimal) {\n [facts, upcomingReminders, peopleContext] = await Promise.all([\n memory.searchFacts(userId, userMessage),\n db.getUpcomingReminders(userId, 15),\n relationships.getPeopleContext(userId, userMessage, 5),\n ]);\n memory.recordFactAccess(userId, facts.map(f => f.key));\n } else {\n facts = [];\n upcomingReminders = [];\n peopleContext = '';\n }\n\n const systemPrompt = await buildSystemPrompt(\n userId, facts, process.env.TIMEZONE || 'UTC', upcomingReminders, peopleContext,\n { minimal, executiveContext }\n );\n\n const messages = [\n { role: 'system', content: systemPrompt }\n ].concat(conversationHistory).concat([\n { role: 'user', content: userMessage }\n ]);\n\n // ── Streaming request to DeepSeek ──────────────────────────────────────\n const response = await axios.post(\n DEEPSEEK_URL,\n {\n model: 'deepseek-chat',\n messages: messages,\n max_tokens: options.maxTokens || 800,\n temperature: 0.3,\n stream: true,\n },\n {\n headers: {\n 'Authorization': 'Bearer ' + process.env.DEEPSEEK_API_KEY,\n 'Content-Type': 'application/json',\n },\n timeout: 30000,\n responseType: 'stream',\n }\n );\n\n // ── Parse SSE stream ───────────────────────────────────────────────────\n let rawText = '';\n let isMessage = null; // null=unknown, true=message, false=tool\n const MESSAGE_PREFIX = '{\"type\":\"message\",\"content\":\"';\n\n await new Promise((resolve, reject) => {\n let buffer = '';\n\n response.data.on('data', (chunk) => {\n buffer += chunk.toString();\n\n // Process complete SSE lines\n const lines = buffer.split('\\n');\n buffer = lines.pop() || ''; // keep incomplete line in buffer\n\n for (const line of lines) {\n if (!line.startsWith('data: ') || line === 'data: [DONE]') continue;\n\n try {\n const json = JSON.parse(line.slice(6)); // strip \"data: \" prefix\n const delta = json.choices?.[0]?.delta?.content;\n if (!delta) continue;\n\n rawText += delta;\n\n // Detect message vs tool on first meaningful tokens\n if (isMessage === null && rawText.length > 10) {\n isMessage = rawText.trimStart().startsWith(MESSAGE_PREFIX);\n }\n\n // 🔥 Show streaming text for message-type responses\n if (isMessage === true && onChunk) {\n let display = rawText;\n // Strip JSON wrapper to show just the content\n display = display.replace(/^\\s*\\{\"type\":\"message\",\"content\":\"/, '');\n // Don't strip trailing quotes until complete (they may be partial)\n if (display.endsWith('\"}') && rawText.endsWith('\"}')) {\n display = display.slice(0, -2);\n }\n onChunk(display);\n }\n } catch {\n // Skip malformed SSE chunks\n }\n }\n });\n\n response.data.on('end', resolve);\n response.data.on('error', reject);\n });\n\n console.log('[DeepSeek] Stream complete (' + rawText.length + ' chars):', rawText.slice(0, 200));\n\n return parseAndValidate(rawText, {\n minimal,\n upcomingReminders,\n userMessage,\n facts,\n });\n}\n\nmodule.exports = { chat, chatStream };\n"}, "afa1e9e431b51f67cf6f8fa7c56b8c6e685cb4ce": {"t": 1785766756.053889, "n": 12338, "h": "89bdc7fac923ab70", "c": "// src/llm/deepseek.js\n// DeepSeek API provider\nrequire('dotenv').config();\nconst axios = require('axios');\nconst db = require('../db');\nconst redisCache = require('../redis');\nconst memory = require('../memory');\nconst relationships = require('../memory/relationships');\nconst { buildSystemPrompt, normalizeLLMResponse } = require('./shared');\nconst validator = require('./validator');\n\nconst DEEPSEEK_URL = (process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com') + '/v1/chat/completions';\n\nasync function chat(userId, userMessage, conversationHistory, options = {}, prefetched = null) {\n if (!conversationHistory) conversationHistory = [];\n\n const minimal = options.minimal === true;\n const executiveContext = options.executiveContext || '';\n\n // Reuse pre-fetched context from llm/index.js, or fetch in parallel\n let facts, upcomingReminders, peopleContext;\n if (prefetched) {\n ({ facts, upcomingReminders, peopleContext } = prefetched);\n } else if (!minimal) {\n [facts, upcomingReminders, peopleContext] = await Promise.all([\n memory.searchFacts(userId, userMessage),\n db.getUpcomingReminders(userId, 15),\n relationships.getPeopleContext(userId, userMessage, 5),\n ]);\n memory.recordFactAccess(userId, facts.map(f => f.key));\n } else {\n facts = [];\n upcomingReminders = [];\n peopleContext = '';\n }\n\n const systemPrompt = await buildSystemPrompt(\n userId, facts, process.env.TIMEZONE || 'UTC', upcomingReminders, peopleContext,\n { minimal, executiveContext }\n );\n\n const messages = [\n { role: 'system', content: systemPrompt }\n ].concat(conversationHistory).concat([\n { role: 'user', content: userMessage }\n ]);\n\n const response = await axios.post(\n DEEPSEEK_URL,\n {\n model: 'deepseek-chat',\n messages: messages,\n max_tokens: options.maxTokens || 800,\n temperature: 0.3,\n },\n {\n headers: {\n 'Authorization': 'Bearer ' + process.env.DEEPSEEK_API_KEY,\n 'Content-Type': 'application/json',\n },\n timeout: 15000,\n }\n );\n\n const rawText = response.data.choices[0].message.content.trim();\n console.log('[DeepSeek] Raw response:', rawText.slice(0, 300));\n\n // ── Try 1: strip markdown fences then parse ───────────────────────────\n try {\n const cleaned = rawText.replace(/^```(?:json)?\\n?/, '').replace(/\\n?```$/, '').trim();\n const parsed = JSON.parse(cleaned);\n const normalized = normalizeLLMResponse(parsed);\n if (normalized) {\n // ✅ Validate cancel_reminder calls against actual reminders (always)\n if (normalized.type === 'tool' && normalized.name === 'cancel_reminder') {\n const cancelValidation = validator.validateCancelReminder(normalized, upcomingReminders, userMessage);\n if (!cancelValidation.isValid) {\n console.warn('[DeepSeek] ⛔️ Blocked invalid cancel_reminder:', cancelValidation.error);\n return {\n type: 'message',\n content: cancelValidation.suggestion || 'I couldn\\'t find that reminder.',\n };\n }\n }\n\n // ⚡ Skip hallucination validator for fast-tier messages (greetings etc.)\n if (!minimal) {\n const validation = validator.validateLLMResponse(normalized, {\n timezone: process.env.TIMEZONE || 'UTC',\n userFacts: facts,\n upcomingReminders: upcomingReminders,\n });\n\n if (!validation.isValid && normalized.type === 'message') {\n console.warn('[DeepSeek] ⚠️ Hallucination detected:', validation.issues.join('; '));\n if (validation.forceToolCall) {\n console.log('[DeepSeek] 🔄 Forcing list_reminders tool call to get accurate times');\n return { type: 'tool', name: 'list_reminders', args: {} };\n }\n const fallback = validator.generateFallbackResponse(userMessage);\n return { type: 'message', content: fallback };\n }\n\n if (validation.issues.length > 0) {\n console.log('[DeepSeek] Validation issues (non-critical):', validation.issues.join('; '));\n }\n }\n\n console.log('[DeepSeek] Parsed OK (type=' + normalized.type + (normalized.name ? ', name=' + normalized.name : '') + ')');\n return normalized;\n }\n // Valid JSON but unrecognized structure — fall through to rawText\n console.log('[DeepSeek] Valid JSON but unrecognized structure, falling back to rawText');\n return { type: 'message', content: rawText };\n } catch (e) {\n // ── Try 2: extract JSON object with regex ───────────────────────────\n const jsonMatch = rawText.match(/\\{[\\s\\S]*\\}/);\n if (jsonMatch) {\n try {\n const extracted = JSON.parse(jsonMatch[0]);\n const normalized = normalizeLLMResponse(extracted);\n if (normalized) {\n // ✅ Validate cancel_reminder calls against actual reminders (always)\n if (normalized.type === 'tool' && normalized.name === 'cancel_reminder') {\n const cancelValidation = validator.validateCancelReminder(normalized, upcomingReminders, userMessage);\n if (!cancelValidation.isValid) {\n console.warn('[DeepSeek] ⛔️ Blocked invalid cancel_reminder:', cancelValidation.error);\n return {\n type: 'message',\n content: cancelValidation.suggestion || 'I couldn\\'t find that reminder.',\n };\n }\n }\n\n // ⚡ Skip hallucination validator for fast-tier messages\n if (!minimal) {\n const validation = validator.validateLLMResponse(normalized, {\n timezone: process.env.TIMEZONE || 'UTC',\n userFacts: facts,\n upcomingReminders: upcomingReminders,\n });\n\n if (!validation.isValid && normalized.type === 'message') {\n console.warn('[DeepSeek] ⚠️ Hallucination detected:', validation.issues.join('; '));\n if (validation.forceToolCall) {\n console.log('[DeepSeek] 🔄 Forcing list_reminders tool call to get accurate times');\n return { type: 'tool', name: 'list_reminders', args: {} };\n }\n const fallback = validator.generateFallbackResponse(userMessage);\n return { type: 'message', content: fallback };\n }\n\n if (validation.issues.length > 0) {\n console.log('[DeepSeek] Validation issues (non-critical):', validation.issues.join('; '));\n }\n }\n\n console.log('[DeepSeek] Regex-extracted JSON OK (type=' + normalized.type + (normalized.name ? ', name=' + normalized.name : '') + ')');\n return normalized;\n }\n console.log('[DeepSeek] Regex-extracted JSON but unrecognized structure');\n } catch (_) {\n console.log('[DeepSeek] Regex extraction found but JSON.parse failed');\n }\n } else {\n console.log('[DeepSeek] No JSON object found in response, treating as plain message');\n }\n return { type: 'message', content: rawText };\n }\n}\n\n/**\n * Streaming version — calls onChunk(displayText) progressively as tokens arrive.\n * Only extracts displayable content for message-type responses.\n * For tool calls, buffers silently and returns the parsed result.\n *\n * @param {string} userId\n * @param {string} userMessage\n * @param {Array} conversationHistory\n * @param {object} options\n * @param {object|null} prefetched\n * @param {function} onChunk - callback(displayText) for each accumulation\n * @returns {Promise<{type:string, content?:string, name?:string, args?:object}>}\n */\nasync function chatStream(userId, userMessage, conversationHistory, options = {}, prefetched = null, onChunk) {\n if (!conversationHistory) conversationHistory = [];\n if (typeof onChunk !== 'function') onChunk = () => { };\n\n const minimal = options.minimal === true;\n const executiveContext = options.executiveContext || '';\n\n let facts, upcomingReminders, peopleContext;\n if (prefetched) {\n ({ facts, upcomingReminders, peopleContext } = prefetched);\n } else if (!minimal) {\n [facts, upcomingReminders, peopleContext] = await Promise.all([\n memory.searchFacts(userId, userMessage),\n db.getUpcomingReminders(userId, 15),\n relationships.getPeopleContext(userId, userMessage, 5),\n ]);\n memory.recordFactAccess(userId, facts.map(f => f.key));\n } else {\n facts = [];\n upcomingReminders = [];\n peopleContext = '';\n }\n\n const systemPrompt = await buildSystemPrompt(\n userId, facts, process.env.TIMEZONE || 'UTC', upcomingReminders, peopleContext,\n { minimal, executiveContext }\n );\n\n const messages = [\n { role: 'system', content: systemPrompt }\n ].concat(conversationHistory).concat([\n { role: 'user', content: userMessage }\n ]);\n\n // ── Streaming request to DeepSeek ──────────────────────────────────────\n const response = await axios.post(\n DEEPSEEK_URL,\n {\n model: 'deepseek-chat',\n messages: messages,\n max_tokens: options.maxTokens || 800,\n temperature: 0.3,\n stream: true,\n },\n {\n headers: {\n 'Authorization': 'Bearer ' + process.env.DEEPSEEK_API_KEY,\n 'Content-Type': 'application/json',\n },\n timeout: 30000,\n responseType: 'stream',\n }\n );\n\n // ── Parse SSE stream ───────────────────────────────────────────────────\n let rawText = '';\n let isMessage = null; // null=unknown, true=message, false=tool\n const MESSAGE_PREFIX = '{\"type\":\"message\",\"content\":\"';\n\n await new Promise((resolve, reject) => {\n let buffer = '';\n\n response.data.on('data', (chunk) => {\n buffer += chunk.toString();\n\n // Process complete SSE lines\n const lines = buffer.split('\\n');\n buffer = lines.pop() || ''; // keep incomplete line in buffer\n\n for (const line of lines) {\n if (!line.startsWith('data: ') || line === 'data: [DONE]') continue;\n\n try {\n const json = JSON.parse(line.slice(6)); // strip \"data: \" prefix\n const delta = json.choices?.[0]?.delta?.content;\n if (!delta) continue;\n\n rawText += delta;\n\n // Detect message vs tool on first meaningful tokens\n if (isMessage === null && rawText.length > 10) {\n isMessage = rawText.trimStart().startsWith(MESSAGE_PREFIX);\n }\n\n // 🔥 Show streaming text for message-type responses\n if (isMessage === true && onChunk) {\n let display = rawText;\n // Strip JSON wrapper to show just the content\n display = display.replace(/^\\s*\\{\"type\":\"message\",\"content\":\"/, '');\n // Don't strip trailing quotes until complete (they may be partial)\n if (display.endsWith('\"}') && rawText.endsWith('\"}')) {\n display = display.slice(0, -2);\n }\n onChunk(display);\n }\n } catch {\n // Skip malformed SSE chunks\n }\n }\n });\n\n response.data.on('end', resolve);\n response.data.on('error', reject);\n });\n\n console.log('[DeepSeek] Stream complete (' + rawText.length + ' chars):', rawText.slice(0, 200));\n\n // ── Parse final response (same logic as chat()) ────────────────────────\n try {\n const cleaned = rawText.replace(/^```(?:json)?\\n?/, '').replace(/\\n?```$/, '').trim();\n const parsed = JSON.parse(cleaned);\n const normalized = normalizeLLMResponse(parsed);\n if (normalized) {\n if (normalized.type === 'tool' && normalized.name === 'cancel_reminder') {\n const cancelValidation = validator.validateCancelReminder(normalized, upcomingReminders, userMessage);\n if (!cancelValidation.isValid) {\n return { type: 'message', content: cancelValidation.suggestion || \"I couldn't find that reminder.\" };\n }\n }\n if (!minimal) {\n const validation = validator.validateLLMResponse(normalized, { timezone: process.env.TIMEZONE || 'UTC', userFacts: facts, upcomingReminders });\n if (!validation.isValid && normalized.type === 'message') {\n if (validation.forceToolCall) return { type: 'tool', name: 'list_reminders', args: {} };\n return { type: 'message', content: validator.generateFallbackResponse(userMessage) };\n }\n }\n return normalized;\n }\n return { type: 'message', content: rawText };\n } catch {\n return { type: 'message', content: rawText };\n }\n}\n\nmodule.exports = { chat, chatStream };\n"}, "154dc493224b3b020b2e52a3e11df849665864f4": {"t": 1785766757.3730266, "n": 7004, "h": "c80572c4f5e3fdf9", "c": "// src/llm/deepseek.js\n// DeepSeek API provider\nrequire('dotenv').config();\nconst axios = require('axios');\nconst db = require('../db');\nconst redisCache = require('../redis');\nconst memory = require('../memory');\nconst relationships = require('../memory/relationships');\nconst { buildSystemPrompt, normalizeLLMResponse } = require('./shared');\nconst validator = require('./validator');\n\nconst DEEPSEEK_URL = (process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com') + '/v1/chat/completions';\n\nasync function chat(userId, userMessage, conversationHistory, options = {}, prefetched = null) {\n if (!conversationHistory) conversationHistory = [];\n\n const minimal = options.minimal === true;\n const executiveContext = options.executiveContext || '';\n\n // Reuse pre-fetched context from llm/index.js, or fetch in parallel\n let facts, upcomingReminders, peopleContext;\n if (prefetched) {\n ({ facts, upcomingReminders, peopleContext } = prefetched);\n } else if (!minimal) {\n [facts, upcomingReminders, peopleContext] = await Promise.all([\n memory.searchFacts(userId, userMessage),\n db.getUpcomingReminders(userId, 15),\n relationships.getPeopleContext(userId, userMessage, 5),\n ]);\n memory.recordFactAccess(userId, facts.map(f => f.key));\n } else {\n facts = [];\n upcomingReminders = [];\n peopleContext = '';\n }\n\n const systemPrompt = await buildSystemPrompt(\n userId, facts, process.env.TIMEZONE || 'UTC', upcomingReminders, peopleContext,\n { minimal, executiveContext }\n );\n\n const messages = [\n { role: 'system', content: systemPrompt }\n ].concat(conversationHistory).concat([\n { role: 'user', content: userMessage }\n ]);\n\n const response = await axios.post(\n DEEPSEEK_URL,\n {\n model: 'deepseek-chat',\n messages: messages,\n max_tokens: options.maxTokens || 800,\n temperature: 0.3,\n },\n {\n headers: {\n 'Authorization': 'Bearer ' + process.env.DEEPSEEK_API_KEY,\n 'Content-Type': 'application/json',\n },\n timeout: 15000,\n }\n );\n\n const rawText = response.data.choices[0].message.content.trim();\n console.log('[DeepSeek] Raw response:', rawText.slice(0, 300));\n\n // ── Try 1: strip markdown fences then parse ───────────────────────────\n try {\n const cleaned = rawText.replace(/^```(?:json)?\\n?/, '').replace(/\\n?```$/, '').trim();\n const parsed = JSON.parse(cleaned);\n const normalized = normalizeLLMResponse(parsed);\n if (normalized) {\n // ✅ Validate cancel_reminder calls against actual reminders (always)\n if (normalized.type === 'tool' && normalized.name === 'cancel_reminder') {\n const cancelValidation = validator.validateCancelReminder(normalized, upcomingReminders, userMessage);\n if (!cancelValidation.isValid) {\n console.warn('[DeepSeek] ⛔️ Blocked invalid cancel_reminder:', cancelValidation.error);\n return {\n type: 'message',\n content: cancelValidation.suggestion || 'I couldn\\'t find that reminder.',\n };\n }\n }\n\n // ⚡ Skip hallucination validator for fast-tier messages (greetings etc.)\n if (!minimal) {\n const validation = validator.validateLLMResponse(normalized, {\n timezone: process.env.TIMEZONE || 'UTC',\n userFacts: facts,\n upcomingReminders: upcomingReminders,\n });\n\n if (!validation.isValid && normalized.type === 'message') {\n console.warn('[DeepSeek] ⚠️ Hallucination detected:', validation.issues.join('; '));\n if (validation.forceToolCall) {\n console.log('[DeepSeek] 🔄 Forcing list_reminders tool call to get accurate times');\n return { type: 'tool', name: 'list_reminders', args: {} };\n }\n const fallback = validator.generateFallbackResponse(userMessage);\n return { type: 'message', content: fallback };\n }\n\n if (validation.issues.length > 0) {\n console.log('[DeepSeek] Validation issues (non-critical):', validation.issues.join('; '));\n }\n }\n\n console.log('[DeepSeek] Parsed OK (type=' + normalized.type + (normalized.name ? ', name=' + normalized.name : '') + ')');\n return normalized;\n }\n // Valid JSON but unrecognized structure — fall through to rawText\n console.log('[DeepSeek] Valid JSON but unrecognized structure, falling back to rawText');\n return { type: 'message', content: rawText };\n } catch (e) {\n // ── Try 2: extract JSON object with regex ───────────────────────────\n const jsonMatch = rawText.match(/\\{[\\s\\S]*\\}/);\n if (jsonMatch) {\n try {\n const extracted = JSON.parse(jsonMatch[0]);\n const normalized = normalizeLLMResponse(extracted);\n if (normalized) {\n // ✅ Validate cancel_reminder calls against actual reminders (always)\n if (normalized.type === 'tool' && normalized.name === 'cancel_reminder') {\n const cancelValidation = validator.validateCancelReminder(normalized, upcomingReminders, userMessage);\n if (!cancelValidation.isValid) {\n console.warn('[DeepSeek] ⛔️ Blocked invalid cancel_reminder:', cancelValidation.error);\n return {\n type: 'message',\n content: cancelValidation.suggestion || 'I couldn\\'t find that reminder.',\n };\n }\n }\n\n // ⚡ Skip hallucination validator for fast-tier messages\n if (!minimal) {\n const validation = validator.validateLLMResponse(normalized, {\n timezone: process.env.TIMEZONE || 'UTC',\n userFacts: facts,\n upcomingReminders: upcomingReminders,\n });\n\n if (!validation.isValid && normalized.type === 'message') {\n console.warn('[DeepSeek] ⚠️ Hallucination detected:', validation.issues.join('; '));\n if (validation.forceToolCall) {\n console.log('[DeepSeek] 🔄 Forcing list_reminders tool call to get accurate times');\n return { type: 'tool', name: 'list_reminders', args: {} };\n }\n const fallback = validator.generateFallbackResponse(userMessage);\n return { type: 'message', content: fallback };\n }\n\n if (validation.issues.length > 0) {\n console.log('[DeepSeek] Validation issues (non-critical):', validation.issues.join('; '));\n }\n }\n\n console.log('[DeepSeek] Regex-extracted JSON OK (type=' + normalized.type + (normalized.name ? ', name=' + normalized.name : '') + ')');\n return normalized;\n }\n console.log('[DeepSeek] Regex-extracted JSON but unrecognized structure');\n } catch (_) {\n console.log('[DeepSeek] Regex extraction found but JSON.parse failed');\n }\n } else {\n console.log('[DeepSeek] No JSON object found in response, treating as plain message');\n }\n return { type: 'message', content: rawText };\n }\n}\n\nmodule.exports = { chat };\n"}, "bcb9f2a1007d0fdb877b828d623b8cf558551c50": {"t": 1785766758.6331291, "n": 6947, "h": "375eeff8260a8306", "c": "// src/llm/deepseek.js\n// DeepSeek API provider\nrequire('dotenv').config();\nconst axios = require('axios');\nconst db = require('../db');\nconst redisCache = require('../redis');\nconst memory = require('../memory');\nconst relationships = require('../memory/relationships');\nconst { buildSystemPrompt, normalizeLLMResponse } = require('./shared');\nconst validator = require('./validator');\n\nconst DEEPSEEK_URL = (process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com') + '/v1/chat/completions';\n\nasync function chat(userId, userMessage, conversationHistory, options = {}) {\n if (!conversationHistory) conversationHistory = [];\n\n const minimal = options.minimal === true;\n const executiveContext = options.executiveContext || '';\n\n // 🔍 Semantic search (skip if minimal mode)\n let facts = [];\n if (!minimal) {\n facts = await memory.searchFacts(userId, userMessage);\n // Record that these facts were accessed (for importance scoring)\n memory.recordFactAccess(userId, facts.map(f => f.key));\n }\n\n // Fetch upcoming reminders (skip if minimal mode)\n const upcomingReminders = minimal ? [] : await db.getUpcomingReminders(userId, 15);\n\n // 👥 Get relevant people context (skip if minimal mode)\n const peopleContext = minimal ? '' : await relationships.getPeopleContext(userId, userMessage, 5);\n\n const systemPrompt = await buildSystemPrompt(\n userId, facts, process.env.TIMEZONE || 'UTC', upcomingReminders, peopleContext,\n { minimal, executiveContext }\n );\n\n const messages = [\n { role: 'system', content: systemPrompt }\n ].concat(conversationHistory).concat([\n { role: 'user', content: userMessage }\n ]);\n\n const response = await axios.post(\n DEEPSEEK_URL,\n {\n model: 'deepseek-chat',\n messages: messages,\n max_tokens: 800,\n temperature: 0.3,\n },\n {\n headers: {\n 'Authorization': 'Bearer ' + process.env.DEEPSEEK_API_KEY,\n 'Content-Type': 'application/json',\n },\n timeout: 15000,\n }\n );\n\n const rawText = response.data.choices[0].message.content.trim();\n console.log('[DeepSeek] Raw response:', rawText.slice(0, 300));\n\n // ── Try 1: strip markdown fences then parse ───────────────────────────\n try {\n const cleaned = rawText.replace(/^```(?:json)?\\n?/, '').replace(/\\n?```$/, '').trim();\n const parsed = JSON.parse(cleaned);\n const normalized = normalizeLLMResponse(parsed);\n if (normalized) {\n // ✅ Validate cancel_reminder calls against actual reminders\n if (normalized.type === 'tool' && normalized.name === 'cancel_reminder') {\n const cancelValidation = validator.validateCancelReminder(normalized, upcomingReminders, userMessage);\n if (!cancelValidation.isValid) {\n console.warn('[DeepSeek] ⛔️ Blocked invalid cancel_reminder:', cancelValidation.error);\n return {\n type: 'message',\n content: cancelValidation.suggestion || 'I couldn\\'t find that reminder.',\n };\n }\n }\n\n // ✅ Validate the response for hallucinations\n const validation = validator.validateLLMResponse(normalized, {\n timezone: process.env.TIMEZONE || 'UTC',\n userFacts: facts,\n upcomingReminders: upcomingReminders,\n });\n\n if (!validation.isValid && normalized.type === 'message') {\n console.warn('[DeepSeek] ⚠️ Hallucination detected:', validation.issues.join('; '));\n // If reminder fabrication, force list_reminders tool call instead of fallback message\n if (validation.forceToolCall) {\n console.log('[DeepSeek] 🔄 Forcing list_reminders tool call to get accurate times');\n return { type: 'tool', name: 'list_reminders', args: {} };\n }\n // Override with a safe clarifying question\n const fallback = validator.generateFallbackResponse(userMessage);\n return { type: 'message', content: fallback };\n }\n\n if (validation.issues.length > 0) {\n console.log('[DeepSeek] Validation issues (non-critical):', validation.issues.join('; '));\n }\n\n console.log('[DeepSeek] Parsed OK (type=' + normalized.type + (normalized.name ? ', name=' + normalized.name : '') + ')');\n return normalized;\n }\n // Valid JSON but unrecognized structure — fall through to rawText\n console.log('[DeepSeek] Valid JSON but unrecognized structure, falling back to rawText');\n return { type: 'message', content: rawText };\n } catch (e) {\n // ── Try 2: extract JSON object with regex ───────────────────────────\n const jsonMatch = rawText.match(/\\{[\\s\\S]*\\}/);\n if (jsonMatch) {\n try {\n const extracted = JSON.parse(jsonMatch[0]);\n const normalized = normalizeLLMResponse(extracted);\n if (normalized) {\n // ✅ Validate cancel_reminder calls against actual reminders\n if (normalized.type === 'tool' && normalized.name === 'cancel_reminder') {\n const cancelValidation = validator.validateCancelReminder(normalized, upcomingReminders, userMessage);\n if (!cancelValidation.isValid) {\n console.warn('[DeepSeek] ⛔️ Blocked invalid cancel_reminder:', cancelValidation.error);\n return {\n type: 'message',\n content: cancelValidation.suggestion || 'I couldn\\'t find that reminder.',\n };\n }\n }\n\n // ✅ Validate the response\n const validation = validator.validateLLMResponse(normalized, {\n timezone: process.env.TIMEZONE || 'UTC',\n userFacts: facts,\n upcomingReminders: upcomingReminders,\n });\n\n if (!validation.isValid && normalized.type === 'message') {\n console.warn('[DeepSeek] ⚠️ Hallucination detected:', validation.issues.join('; '));\n // If reminder fabrication, force list_reminders tool call\n if (validation.forceToolCall) {\n console.log('[DeepSeek] 🔄 Forcing list_reminders tool call to get accurate times');\n return { type: 'tool', name: 'list_reminders', args: {} };\n }\n const fallback = validator.generateFallbackResponse(userMessage);\n return { type: 'message', content: fallback };\n }\n\n if (validation.issues.length > 0) {\n console.log('[DeepSeek] Validation issues (non-critical):', validation.issues.join('; '));\n }\n\n console.log('[DeepSeek] Regex-extracted JSON OK (type=' + normalized.type + (normalized.name ? ', name=' + normalized.name : '') + ')');\n return normalized;\n }\n console.log('[DeepSeek] Regex-extracted JSON but unrecognized structure');\n } catch (_) {\n console.log('[DeepSeek] Regex extraction found but JSON.parse failed');\n }\n } else {\n console.log('[DeepSeek] No JSON object found in response, treating as plain message');\n }\n return { type: 'message', content: rawText };\n }\n}\n\nmodule.exports = { chat };\n"}, "0682d8b235a27d6ac24d450f363828990b18f7c1": {"t": 1785766760.0023546, "n": 6243, "h": "fcc3df96b845c422", "c": "// src/llm/deepseek.js\n// DeepSeek API provider\nrequire('dotenv').config();\nconst axios = require('axios');\nconst db = require('../db');\nconst redisCache = require('../redis');\nconst memory = require('../memory');\nconst relationships = require('../memory/relationships');\nconst { buildSystemPrompt, normalizeLLMResponse } = require('./shared');\nconst validator = require('./validator');\n\nconst DEEPSEEK_URL = (process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com') + '/v1/chat/completions';\n\nasync function chat(userId, userMessage, conversationHistory, options = {}) {\n if (!conversationHistory) conversationHistory = [];\n\n const minimal = options.minimal === true;\n const executiveContext = options.executiveContext || '';\n\n // 🔍 Semantic search (skip if minimal mode)\n let facts = [];\n if (!minimal) {\n facts = await memory.searchFacts(userId, userMessage);\n // Record that these facts were accessed (for importance scoring)\n memory.recordFactAccess(userId, facts.map(f => f.key));\n }\n\n // Fetch upcoming reminders (skip if minimal mode)\n const upcomingReminders = minimal ? [] : await db.getUpcomingReminders(userId, 15);\n\n // 👥 Get relevant people context (skip if minimal mode)\n const peopleContext = minimal ? '' : await relationships.getPeopleContext(userId, userMessage, 5);\n\n const systemPrompt = await buildSystemPrompt(\n userId, facts, process.env.TIMEZONE || 'UTC', upcomingReminders, peopleContext,\n { minimal, executiveContext }\n );\n\n const messages = [\n { role: 'system', content: systemPrompt }\n ].concat(conversationHistory).concat([\n { role: 'user', content: userMessage }\n ]);\n\n const response = await axios.post(\n DEEPSEEK_URL,\n {\n model: 'deepseek-chat',\n messages: messages,\n max_tokens: 800,\n temperature: 0.3,\n },\n {\n headers: {\n 'Authorization': 'Bearer ' + process.env.DEEPSEEK_API_KEY,\n 'Content-Type': 'application/json',\n },\n timeout: 15000,\n }\n );\n\n const rawText = response.data.choices[0].message.content.trim();\n console.log('[DeepSeek] Raw response:', rawText.slice(0, 300));\n\n // ── Try 1: strip markdown fences then parse ───────────────────────────\n try {\n const cleaned = rawText.replace(/^```(?:json)?\\n?/, '').replace(/\\n?```$/, '').trim();\n const parsed = JSON.parse(cleaned);\n const normalized = normalizeLLMResponse(parsed);\n if (normalized) {\n // ✅ Validate cancel_reminder calls against actual reminders\n if (normalized.type === 'tool' && normalized.name === 'cancel_reminder') {\n const cancelValidation = validator.validateCancelReminder(normalized, upcomingReminders, userMessage);\n if (!cancelValidation.isValid) {\n console.warn('[DeepSeek] ⛔️ Blocked invalid cancel_reminder:', cancelValidation.error);\n return {\n type: 'message',\n content: cancelValidation.suggestion || 'I couldn\\'t find that reminder.',\n };\n }\n }\n\n // ✅ Validate the response for hallucinations\n const validation = validator.validateLLMResponse(normalized, {\n timezone: process.env.TIMEZONE || 'UTC',\n userFacts: facts,\n });\n\n if (!validation.isValid && normalized.type === 'message') {\n console.warn('[DeepSeek] ⚠️ Hallucination detected:', validation.issues.join('; '));\n // Override with a safe clarifying question\n const fallback = validator.generateFallbackResponse(userMessage);\n return { type: 'message', content: fallback };\n }\n\n if (validation.issues.length > 0) {\n console.log('[DeepSeek] Validation issues (non-critical):', validation.issues.join('; '));\n }\n\n console.log('[DeepSeek] Parsed OK (type=' + normalized.type + (normalized.name ? ', name=' + normalized.name : '') + ')');\n return normalized;\n }\n // Valid JSON but unrecognized structure — fall through to rawText\n console.log('[DeepSeek] Valid JSON but unrecognized structure, falling back to rawText');\n return { type: 'message', content: rawText };\n } catch (e) {\n // ── Try 2: extract JSON object with regex ───────────────────────────\n const jsonMatch = rawText.match(/\\{[\\s\\S]*\\}/);\n if (jsonMatch) {\n try {\n const extracted = JSON.parse(jsonMatch[0]);\n const normalized = normalizeLLMResponse(extracted);\n if (normalized) {\n // ✅ Validate cancel_reminder calls against actual reminders\n if (normalized.type === 'tool' && normalized.name === 'cancel_reminder') {\n const cancelValidation = validator.validateCancelReminder(normalized, upcomingReminders, userMessage);\n if (!cancelValidation.isValid) {\n console.warn('[DeepSeek] ⛔️ Blocked invalid cancel_reminder:', cancelValidation.error);\n return {\n type: 'message',\n content: cancelValidation.suggestion || 'I couldn\\'t find that reminder.',\n };\n }\n }\n\n // ✅ Validate the response\n const validation = validator.validateLLMResponse(normalized, {\n timezone: process.env.TIMEZONE || 'UTC',\n userFacts: facts,\n });\n\n if (!validation.isValid && normalized.type === 'message') {\n console.warn('[DeepSeek] ⚠️ Hallucination detected:', validation.issues.join('; '));\n const fallback = validator.generateFallbackResponse(userMessage);\n return { type: 'message', content: fallback };\n }\n\n if (validation.issues.length > 0) {\n console.log('[DeepSeek] Validation issues (non-critical):', validation.issues.join('; '));\n }\n\n console.log('[DeepSeek] Regex-extracted JSON OK (type=' + normalized.type + (normalized.name ? ', name=' + normalized.name : '') + ')');\n return normalized;\n }\n console.log('[DeepSeek] Regex-extracted JSON but unrecognized structure');\n } catch (_) {\n console.log('[DeepSeek] Regex extraction found but JSON.parse failed');\n }\n } else {\n console.log('[DeepSeek] No JSON object found in response, treating as plain message');\n }\n return { type: 'message', content: rawText };\n }\n}\n\nmodule.exports = { chat };\n"}, "2c025be150abc210e00a5163b316e39bcab53a05": {"t": 1785766761.1706736, "n": 6052, "h": "8f69bf460dc43500", "c": "// src/llm/deepseek.js\n// DeepSeek API provider\nrequire('dotenv').config();\nconst axios = require('axios');\nconst db = require('../db');\nconst redisCache = require('../redis');\nconst memory = require('../memory');\nconst relationships = require('../memory/relationships');\nconst { buildSystemPrompt, normalizeLLMResponse } = require('./shared');\nconst validator = require('./validator');\n\nconst DEEPSEEK_URL = (process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com') + '/v1/chat/completions';\n\nasync function chat(userId, userMessage, conversationHistory) {\n if (!conversationHistory) conversationHistory = [];\n\n // 🔍 Semantic search: only retrieve facts relevant to user's current message\n let facts = await memory.searchFacts(userId, userMessage);\n\n // Record that these facts were accessed (for importance scoring)\n memory.recordFactAccess(userId, facts.map(f => f.key));\n\n // Fetch upcoming reminders so the LLM can reference them by ID for update/cancel\n const upcomingReminders = await db.getUpcomingReminders(userId, 15);\n\n // 👥 Get relevant people context\n const peopleContext = await relationships.getPeopleContext(userId, userMessage, 5);\n\n const systemPrompt = await buildSystemPrompt(userId, facts, process.env.TIMEZONE || 'UTC', upcomingReminders, peopleContext);\n\n const messages = [\n { role: 'system', content: systemPrompt }\n ].concat(conversationHistory).concat([\n { role: 'user', content: userMessage }\n ]);\n\n const response = await axios.post(\n DEEPSEEK_URL,\n {\n model: 'deepseek-chat',\n messages: messages,\n max_tokens: 800,\n temperature: 0.3,\n },\n {\n headers: {\n 'Authorization': 'Bearer ' + process.env.DEEPSEEK_API_KEY,\n 'Content-Type': 'application/json',\n },\n timeout: 15000,\n }\n );\n\n const rawText = response.data.choices[0].message.content.trim();\n console.log('[DeepSeek] Raw response:', rawText.slice(0, 300));\n\n // ── Try 1: strip markdown fences then parse ───────────────────────────\n try {\n const cleaned = rawText.replace(/^```(?:json)?\\n?/, '').replace(/\\n?```$/, '').trim();\n const parsed = JSON.parse(cleaned);\n const normalized = normalizeLLMResponse(parsed);\n if (normalized) {\n // ✅ Validate cancel_reminder calls against actual reminders\n if (normalized.type === 'tool' && normalized.name === 'cancel_reminder') {\n const cancelValidation = validator.validateCancelReminder(normalized, upcomingReminders, userMessage);\n if (!cancelValidation.isValid) {\n console.warn('[DeepSeek] ⛔️ Blocked invalid cancel_reminder:', cancelValidation.error);\n return {\n type: 'message',\n content: cancelValidation.suggestion || 'I couldn\\'t find that reminder.',\n };\n }\n }\n\n // ✅ Validate the response for hallucinations\n const validation = validator.validateLLMResponse(normalized, {\n timezone: process.env.TIMEZONE || 'UTC',\n userFacts: facts,\n });\n\n if (!validation.isValid && normalized.type === 'message') {\n console.warn('[DeepSeek] ⚠️ Hallucination detected:', validation.issues.join('; '));\n // Override with a safe clarifying question\n const fallback = validator.generateFallbackResponse(userMessage);\n return { type: 'message', content: fallback };\n }\n\n if (validation.issues.length > 0) {\n console.log('[DeepSeek] Validation issues (non-critical):', validation.issues.join('; '));\n }\n\n console.log('[DeepSeek] Parsed OK (type=' + normalized.type + (normalized.name ? ', name=' + normalized.name : '') + ')');\n return normalized;\n }\n // Valid JSON but unrecognized structure — fall through to rawText\n console.log('[DeepSeek] Valid JSON but unrecognized structure, falling back to rawText');\n return { type: 'message', content: rawText };\n } catch (e) {\n // ── Try 2: extract JSON object with regex ───────────────────────────\n const jsonMatch = rawText.match(/\\{[\\s\\S]*\\}/);\n if (jsonMatch) {\n try {\n const extracted = JSON.parse(jsonMatch[0]);\n const normalized = normalizeLLMResponse(extracted);\n if (normalized) {\n // ✅ Validate cancel_reminder calls against actual reminders\n if (normalized.type === 'tool' && normalized.name === 'cancel_reminder') {\n const cancelValidation = validator.validateCancelReminder(normalized, upcomingReminders, userMessage);\n if (!cancelValidation.isValid) {\n console.warn('[DeepSeek] ⛔️ Blocked invalid cancel_reminder:', cancelValidation.error);\n return {\n type: 'message',\n content: cancelValidation.suggestion || 'I couldn\\'t find that reminder.',\n };\n }\n }\n\n // ✅ Validate the response\n const validation = validator.validateLLMResponse(normalized, {\n timezone: process.env.TIMEZONE || 'UTC',\n userFacts: facts,\n });\n\n if (!validation.isValid && normalized.type === 'message') {\n console.warn('[DeepSeek] ⚠️ Hallucination detected:', validation.issues.join('; '));\n const fallback = validator.generateFallbackResponse(userMessage);\n return { type: 'message', content: fallback };\n }\n\n if (validation.issues.length > 0) {\n console.log('[DeepSeek] Validation issues (non-critical):', validation.issues.join('; '));\n }\n\n console.log('[DeepSeek] Regex-extracted JSON OK (type=' + normalized.type + (normalized.name ? ', name=' + normalized.name : '') + ')');\n return normalized;\n }\n console.log('[DeepSeek] Regex-extracted JSON but unrecognized structure');\n } catch (_) {\n console.log('[DeepSeek] Regex extraction found but JSON.parse failed');\n }\n } else {\n console.log('[DeepSeek] No JSON object found in response, treating as plain message');\n }\n return { type: 'message', content: rawText };\n }\n}\n\nmodule.exports = { chat };\n"}, "07d531355a30af249a5bbcc23bbffde23d2d1943": {"t": 1785766762.4864008, "n": 3443, "h": "6ea25b6b3a616cbe", "c": "// src/llm/deepseek.js\n// DeepSeek API provider\nrequire('dotenv').config();\nconst axios = require('axios');\nconst db = require('../db');\nconst redisCache = require('../redis');\nconst memory = require('../memory');\nconst relationships = require('../memory/relationships');\nconst { buildSystemPrompt, normalizeLLMResponse } = require('./shared');\n\nconst DEEPSEEK_URL = (process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com') + '/v1/chat/completions';\n\nasync function chat(userId, userMessage, conversationHistory) {\n if (!conversationHistory) conversationHistory = [];\n\n // 🔍 Semantic search: only retrieve facts relevant to user's current message\n let facts = await memory.searchFacts(userId, userMessage);\n\n // Record that these facts were accessed (for importance scoring)\n memory.recordFactAccess(userId, facts.map(f => f.key));\n\n // Fetch upcoming reminders so the LLM can reference them by ID for update/cancel\n const upcomingReminders = await db.getUpcomingReminders(userId, 15);\n\n // 👥 Get relevant people context\n const peopleContext = await relationships.getPeopleContext(userId, userMessage, 5);\n\n const systemPrompt = await buildSystemPrompt(userId, facts, process.env.TIMEZONE || 'UTC', upcomingReminders, peopleContext);\n\n const messages = [\n { role: 'system', content: systemPrompt }\n ].concat(conversationHistory).concat([\n { role: 'user', content: userMessage }\n ]);\n\n const response = await axios.post(\n DEEPSEEK_URL,\n {\n model: 'deepseek-chat',\n messages: messages,\n max_tokens: 800,\n temperature: 0.3,\n },\n {\n headers: {\n 'Authorization': 'Bearer ' + process.env.DEEPSEEK_API_KEY,\n 'Content-Type': 'application/json',\n },\n timeout: 15000,\n }\n );\n\n const rawText = response.data.choices[0].message.content.trim();\n console.log('[DeepSeek] Raw response:', rawText.slice(0, 300));\n\n // ── Try 1: strip markdown fences then parse ───────────────────────────\n try {\n const cleaned = rawText.replace(/^```(?:json)?\\n?/, '').replace(/\\n?```$/, '').trim();\n const parsed = JSON.parse(cleaned);\n const normalized = normalizeLLMResponse(parsed);\n if (normalized) {\n console.log('[DeepSeek] Parsed OK (type=' + normalized.type + (normalized.name ? ', name=' + normalized.name : '') + ')');\n return normalized;\n }\n // Valid JSON but unrecognized structure — fall through to rawText\n console.log('[DeepSeek] Valid JSON but unrecognized structure, falling back to rawText');\n return { type: 'message', content: rawText };\n } catch (e) {\n // ── Try 2: extract JSON object with regex ───────────────────────────\n const jsonMatch = rawText.match(/\\{[\\s\\S]*\\}/);\n if (jsonMatch) {\n try {\n const extracted = JSON.parse(jsonMatch[0]);\n const normalized = normalizeLLMResponse(extracted);\n if (normalized) {\n console.log('[DeepSeek] Regex-extracted JSON OK (type=' + normalized.type + (normalized.name ? ', name=' + normalized.name : '') + ')');\n return normalized;\n }\n console.log('[DeepSeek] Regex-extracted JSON but unrecognized structure');\n } catch (_) {\n console.log('[DeepSeek] Regex extraction found but JSON.parse failed');\n }\n } else {\n console.log('[DeepSeek] No JSON object found in response, treating as plain message');\n }\n return { type: 'message', content: rawText };\n }\n}\n\nmodule.exports = { chat };\n"}, "adb1298ac0e22c7f5a2ab3844050d093c757a636": {"t": 1785766763.7444243, "n": 3248, "h": "5d70fd97362cbb44", "c": "// src/llm/deepseek.js\n// DeepSeek API provider\nrequire('dotenv').config();\nconst axios = require('axios');\nconst db = require('../db');\nconst redisCache = require('../redis');\nconst memory = require('../memory');\nconst { buildSystemPrompt, normalizeLLMResponse } = require('./shared');\n\nconst DEEPSEEK_URL = (process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com') + '/v1/chat/completions';\n\nasync function chat(userId, userMessage, conversationHistory) {\n if (!conversationHistory) conversationHistory = [];\n\n // 🔍 Semantic search: only retrieve facts relevant to user's current message\n let facts = await memory.searchFacts(userId, userMessage);\n\n // Record that these facts were accessed (for importance scoring)\n memory.recordFactAccess(userId, facts.map(f => f.key));\n\n // Fetch upcoming reminders so the LLM can reference them by ID for update/cancel\n const upcomingReminders = await db.getUpcomingReminders(userId, 15);\n\n const systemPrompt = await buildSystemPrompt(userId, facts, process.env.TIMEZONE || 'UTC', upcomingReminders);\n\n const messages = [\n { role: 'system', content: systemPrompt }\n ].concat(conversationHistory).concat([\n { role: 'user', content: userMessage }\n ]);\n\n const response = await axios.post(\n DEEPSEEK_URL,\n {\n model: 'deepseek-chat',\n messages: messages,\n max_tokens: 800,\n temperature: 0.3,\n },\n {\n headers: {\n 'Authorization': 'Bearer ' + process.env.DEEPSEEK_API_KEY,\n 'Content-Type': 'application/json',\n },\n timeout: 15000,\n }\n );\n\n const rawText = response.data.choices[0].message.content.trim();\n console.log('[DeepSeek] Raw response:', rawText.slice(0, 300));\n\n // ── Try 1: strip markdown fences then parse ───────────────────────────\n try {\n const cleaned = rawText.replace(/^```(?:json)?\\n?/, '').replace(/\\n?```$/, '').trim();\n const parsed = JSON.parse(cleaned);\n const normalized = normalizeLLMResponse(parsed);\n if (normalized) {\n console.log('[DeepSeek] Parsed OK (type=' + normalized.type + (normalized.name ? ', name=' + normalized.name : '') + ')');\n return normalized;\n }\n // Valid JSON but unrecognized structure — fall through to rawText\n console.log('[DeepSeek] Valid JSON but unrecognized structure, falling back to rawText');\n return { type: 'message', content: rawText };\n } catch (e) {\n // ── Try 2: extract JSON object with regex ───────────────────────────\n const jsonMatch = rawText.match(/\\{[\\s\\S]*\\}/);\n if (jsonMatch) {\n try {\n const extracted = JSON.parse(jsonMatch[0]);\n const normalized = normalizeLLMResponse(extracted);\n if (normalized) {\n console.log('[DeepSeek] Regex-extracted JSON OK (type=' + normalized.type + (normalized.name ? ', name=' + normalized.name : '') + ')');\n return normalized;\n }\n console.log('[DeepSeek] Regex-extracted JSON but unrecognized structure');\n } catch (_) {\n console.log('[DeepSeek] Regex extraction found but JSON.parse failed');\n }\n } else {\n console.log('[DeepSeek] No JSON object found in response, treating as plain message');\n }\n return { type: 'message', content: rawText };\n }\n}\n\nmodule.exports = { chat };\n"}, "671dc9f42adbde5e9559252dcb79f1642262f185": {"t": 1785766765.1058471, "n": 3211, "h": "aa22f8d67e852a6b", "c": "// src/llm/deepseek.js\n// DeepSeek API provider\nrequire('dotenv').config();\nconst axios = require('axios');\nconst db = require('../db');\nconst redisCache = require('../redis');\nconst { buildSystemPrompt, normalizeLLMResponse } = require('./shared');\n\nconst DEEPSEEK_URL = (process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com') + '/v1/chat/completions';\n\nasync function chat(userId, userMessage, conversationHistory) {\n if (!conversationHistory) conversationHistory = [];\n\n // Try Redis cache first, fall back to DB\n let facts = await redisCache.getFactsCache(userId);\n if (facts === null) {\n facts = await db.getAllFacts(userId);\n // Populate cache for next time (fire-and-forget)\n redisCache.setFactsCache(userId, facts);\n }\n\n // Fetch upcoming reminders so the LLM can reference them by ID for update/cancel\n const upcomingReminders = await db.getUpcomingReminders(userId, 15);\n\n const systemPrompt = await buildSystemPrompt(userId, facts, process.env.TIMEZONE || 'UTC', upcomingReminders);\n\n const messages = [\n { role: 'system', content: systemPrompt }\n ].concat(conversationHistory).concat([\n { role: 'user', content: userMessage }\n ]);\n\n const response = await axios.post(\n DEEPSEEK_URL,\n {\n model: 'deepseek-chat',\n messages: messages,\n max_tokens: 800,\n temperature: 0.3,\n },\n {\n headers: {\n 'Authorization': 'Bearer ' + process.env.DEEPSEEK_API_KEY,\n 'Content-Type': 'application/json',\n },\n timeout: 15000,\n }\n );\n\n const rawText = response.data.choices[0].message.content.trim();\n console.log('[DeepSeek] Raw response:', rawText.slice(0, 300));\n\n // ── Try 1: strip markdown fences then parse ───────────────────────────\n try {\n const cleaned = rawText.replace(/^```(?:json)?\\n?/, '').replace(/\\n?```$/, '').trim();\n const parsed = JSON.parse(cleaned);\n const normalized = normalizeLLMResponse(parsed);\n if (normalized) {\n console.log('[DeepSeek] Parsed OK (type=' + normalized.type + (normalized.name ? ', name=' + normalized.name : '') + ')');\n return normalized;\n }\n // Valid JSON but unrecognized structure — fall through to rawText\n console.log('[DeepSeek] Valid JSON but unrecognized structure, falling back to rawText');\n return { type: 'message', content: rawText };\n } catch (e) {\n // ── Try 2: extract JSON object with regex ───────────────────────────\n const jsonMatch = rawText.match(/\\{[\\s\\S]*\\}/);\n if (jsonMatch) {\n try {\n const extracted = JSON.parse(jsonMatch[0]);\n const normalized = normalizeLLMResponse(extracted);\n if (normalized) {\n console.log('[DeepSeek] Regex-extracted JSON OK (type=' + normalized.type + (normalized.name ? ', name=' + normalized.name : '') + ')');\n return normalized;\n }\n console.log('[DeepSeek] Regex-extracted JSON but unrecognized structure');\n } catch (_) {\n console.log('[DeepSeek] Regex extraction found but JSON.parse failed');\n }\n } else {\n console.log('[DeepSeek] No JSON object found in response, treating as plain message');\n }\n return { type: 'message', content: rawText };\n }\n}\n\nmodule.exports = { chat };\n"}, "2f33113d42a41c889feafba808b5479f24818537": {"t": 1785766766.9840386, "n": 3197, "h": "cad580f5606a41f3", "c": "// src/llm/deepseek.js\n// DeepSeek API provider\nrequire('dotenv').config();\nconst axios = require('axios');\nconst db = require('../db');\nconst redisCache = require('../redis');\nconst { buildSystemPrompt, normalizeLLMResponse } = require('./shared');\n\nconst DEEPSEEK_URL = (process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com') + '/v1/chat/completions';\n\nasync function chat(userId, userMessage, conversationHistory) {\n if (!conversationHistory) conversationHistory = [];\n\n // Try Redis cache first, fall back to DB\n let facts = await redisCache.getFactsCache(userId);\n if (facts === null) {\n facts = await db.getAllFacts(userId);\n // Populate cache for next time (fire-and-forget)\n redisCache.setFactsCache(userId, facts);\n }\n\n // Fetch upcoming reminders so the LLM can reference them by ID for update/cancel\n const upcomingReminders = await db.getUpcomingReminders(userId, 15);\n\n const systemPrompt = buildSystemPrompt(facts, process.env.TIMEZONE || 'UTC', upcomingReminders);\n\n const messages = [\n { role: 'system', content: systemPrompt }\n ].concat(conversationHistory).concat([\n { role: 'user', content: userMessage }\n ]);\n\n const response = await axios.post(\n DEEPSEEK_URL,\n {\n model: 'deepseek-chat',\n messages: messages,\n max_tokens: 800,\n temperature: 0.3,\n },\n {\n headers: {\n 'Authorization': 'Bearer ' + process.env.DEEPSEEK_API_KEY,\n 'Content-Type': 'application/json',\n },\n timeout: 15000,\n }\n );\n\n const rawText = response.data.choices[0].message.content.trim();\n console.log('[DeepSeek] Raw response:', rawText.slice(0, 300));\n\n // ── Try 1: strip markdown fences then parse ───────────────────────────\n try {\n const cleaned = rawText.replace(/^```(?:json)?\\n?/, '').replace(/\\n?```$/, '').trim();\n const parsed = JSON.parse(cleaned);\n const normalized = normalizeLLMResponse(parsed);\n if (normalized) {\n console.log('[DeepSeek] Parsed OK (type=' + normalized.type + (normalized.name ? ', name=' + normalized.name : '') + ')');\n return normalized;\n }\n // Valid JSON but unrecognized structure — fall through to rawText\n console.log('[DeepSeek] Valid JSON but unrecognized structure, falling back to rawText');\n return { type: 'message', content: rawText };\n } catch (e) {\n // ── Try 2: extract JSON object with regex ───────────────────────────\n const jsonMatch = rawText.match(/\\{[\\s\\S]*\\}/);\n if (jsonMatch) {\n try {\n const extracted = JSON.parse(jsonMatch[0]);\n const normalized = normalizeLLMResponse(extracted);\n if (normalized) {\n console.log('[DeepSeek] Regex-extracted JSON OK (type=' + normalized.type + (normalized.name ? ', name=' + normalized.name : '') + ')');\n return normalized;\n }\n console.log('[DeepSeek] Regex-extracted JSON but unrecognized structure');\n } catch (_) {\n console.log('[DeepSeek] Regex extraction found but JSON.parse failed');\n }\n } else {\n console.log('[DeepSeek] No JSON object found in response, treating as plain message');\n }\n return { type: 'message', content: rawText };\n }\n}\n\nmodule.exports = { chat };\n"}, "933dd2d0b36d68b6a1f5b9399fffc1d4fa239b0a": {"t": 1785766768.4289615, "n": 3042, "h": "742bce8a61faad3a", "c": "// src/llm/deepseek.js\n// DeepSeek API provider\nrequire('dotenv').config();\nconst axios = require('axios');\nconst db = require('../db');\nconst redisCache = require('../redis');\nconst { buildSystemPrompt } = require('./shared');\n\nconst DEEPSEEK_URL = (process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com') + '/v1/chat/completions';\n\nasync function chat(userId, userMessage, conversationHistory) {\n if (!conversationHistory) conversationHistory = [];\n\n // Try Redis cache first, fall back to DB\n let facts = await redisCache.getFactsCache(userId);\n if (facts === null) {\n facts = await db.getAllFacts(userId);\n // Populate cache for next time (fire-and-forget)\n redisCache.setFactsCache(userId, facts);\n }\n\n // Fetch upcoming reminders so the LLM can reference them by ID for update/cancel\n const upcomingReminders = await db.getUpcomingReminders(userId, 15);\n\n const systemPrompt = buildSystemPrompt(facts, process.env.TIMEZONE || 'UTC', upcomingReminders);\n\n const messages = [\n { role: 'system', content: systemPrompt }\n ].concat(conversationHistory).concat([\n { role: 'user', content: userMessage }\n ]);\n\n const response = await axios.post(\n DEEPSEEK_URL,\n {\n model: 'deepseek-chat',\n messages: messages,\n max_tokens: 800,\n temperature: 0.3,\n },\n {\n headers: {\n 'Authorization': 'Bearer ' + process.env.DEEPSEEK_API_KEY,\n 'Content-Type': 'application/json',\n },\n timeout: 15000,\n }\n );\n\n const rawText = response.data.choices[0].message.content.trim();\n console.log('[DeepSeek] Raw response:', rawText.slice(0, 300));\n\n // ── Try 1: strip markdown fences then parse ───────────────────────────\n try {\n const cleaned = rawText.replace(/^```(?:json)?\\n?/, '').replace(/\\n?```$/, '').trim();\n const parsed = JSON.parse(cleaned);\n if (parsed.type === 'message' || parsed.type === 'tool') {\n console.log('[DeepSeek] Parsed OK (type=' + parsed.type + ')');\n return parsed;\n }\n // Valid JSON but wrong type — fall through to rawText\n console.log('[DeepSeek] Valid JSON but unexpected type=' + parsed.type + ', falling back to rawText');\n return { type: 'message', content: rawText };\n } catch (e) {\n // ── Try 2: extract JSON object with regex ───────────────────────────\n const jsonMatch = rawText.match(/\\{[\\s\\S]*\\}/);\n if (jsonMatch) {\n try {\n const extracted = JSON.parse(jsonMatch[0]);\n if (extracted.type === 'message' || extracted.type === 'tool') {\n console.log('[DeepSeek] Regex-extracted JSON OK (type=' + extracted.type + ')');\n return extracted;\n }\n console.log('[DeepSeek] Regex-extracted JSON but unexpected type=' + extracted.type);\n } catch (_) {\n console.log('[DeepSeek] Regex extraction found but JSON.parse failed');\n }\n } else {\n console.log('[DeepSeek] No JSON object found in response, treating as plain message');\n }\n return { type: 'message', content: rawText };\n }\n}\n\nmodule.exports = { chat };\n"}, "2d0602496851caa88e014a261b62bf23a82569ed": {"t": 1785766769.8557942, "n": 2153, "h": "608807fe46fe45df", "c": "// src/llm/deepseek.js\n// DeepSeek API provider\nrequire('dotenv').config();\nconst axios = require('axios');\nconst db = require('../db');\nconst redisCache = require('../redis');\nconst { buildSystemPrompt } = require('./shared');\n\nconst DEEPSEEK_URL = (process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com') + '/v1/chat/completions';\n\nasync function chat(userId, userMessage, conversationHistory) {\n if (!conversationHistory) conversationHistory = [];\n\n // Try Redis cache first, fall back to DB\n let facts = await redisCache.getFactsCache(userId);\n if (facts === null) {\n facts = await db.getAllFacts(userId);\n // Populate cache for next time (fire-and-forget)\n redisCache.setFactsCache(userId, facts);\n }\n\n const systemPrompt = buildSystemPrompt(facts, process.env.TIMEZONE || 'UTC');\n\n const messages = [\n { role: 'system', content: systemPrompt }\n ].concat(conversationHistory).concat([\n { role: 'user', content: userMessage }\n ]);\n\n const response = await axios.post(\n DEEPSEEK_URL,\n {\n model: 'deepseek-chat',\n messages: messages,\n max_tokens: 500,\n temperature: 0.3,\n },\n {\n headers: {\n 'Authorization': 'Bearer ' + process.env.DEEPSEEK_API_KEY,\n 'Content-Type': 'application/json',\n },\n timeout: 15000,\n }\n );\n\n const rawText = response.data.choices[0].message.content.trim();\n\n try {\n const cleaned = rawText.replace(/^```(?:json)?\\n?/, '').replace(/\\n?```$/, '').trim();\n const parsed = JSON.parse(cleaned);\n if (parsed.type === 'message' || parsed.type === 'tool') {\n return parsed;\n }\n return { type: 'message', content: rawText };\n } catch (e) {\n // JSON.parse failed on full text — try to extract just the JSON object\n const jsonMatch = rawText.match(/\\{[\\s\\S]*\\}/);\n if (jsonMatch) {\n try {\n const extracted = JSON.parse(jsonMatch[0]);\n if (extracted.type === 'message' || extracted.type === 'tool') {\n return extracted;\n }\n } catch (_) { /* still couldn't parse, fall through */ }\n }\n return { type: 'message', content: rawText };\n }\n}\n\nmodule.exports = { chat };\n"}, "5b20c47200440b6b1a8e562c3cf617d6244b2b84": {"t": 1785766771.1582212, "n": 1760, "h": "3db128e4f31ff292", "c": "// src/llm/deepseek.js\n// DeepSeek API provider\nrequire('dotenv').config();\nconst axios = require('axios');\nconst db = require('../db');\nconst redisCache = require('../redis');\nconst { buildSystemPrompt } = require('./shared');\n\nconst DEEPSEEK_URL = (process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com') + '/v1/chat/completions';\n\nasync function chat(userId, userMessage, conversationHistory) {\n if (!conversationHistory) conversationHistory = [];\n\n // Try Redis cache first, fall back to DB\n let facts = await redisCache.getFactsCache(userId);\n if (facts === null) {\n facts = await db.getAllFacts(userId);\n // Populate cache for next time (fire-and-forget)\n redisCache.setFactsCache(userId, facts);\n }\n\n const systemPrompt = buildSystemPrompt(facts, process.env.TIMEZONE || 'UTC');\n\n const messages = [\n { role: 'system', content: systemPrompt }\n ].concat(conversationHistory).concat([\n { role: 'user', content: userMessage }\n ]);\n\n const response = await axios.post(\n DEEPSEEK_URL,\n {\n model: 'deepseek-chat',\n messages: messages,\n max_tokens: 500,\n temperature: 0.3,\n },\n {\n headers: {\n 'Authorization': 'Bearer ' + process.env.DEEPSEEK_API_KEY,\n 'Content-Type': 'application/json',\n },\n timeout: 15000,\n }\n );\n\n const rawText = response.data.choices[0].message.content.trim();\n\n try {\n const cleaned = rawText.replace(/^```(?:json)?\\n?/, '').replace(/\\n?```$/, '').trim();\n const parsed = JSON.parse(cleaned);\n if (parsed.type === 'message' || parsed.type === 'tool') {\n return parsed;\n }\n return { type: 'message', content: rawText };\n } catch (e) {\n return { type: 'message', content: rawText };\n }\n}\n\nmodule.exports = { chat };\n"}, "91916d615ce9d58bc402693c0649cbca08b83ddd": {"t": 1785766772.4348516, "n": 3182, "h": "d0429aab6d3684c9", "c": "// src/llm/deepseek.js\n// Handles all communication with DeepSeek API\nrequire('dotenv').config();\nconst axios = require('axios');\nconst db = require('../db');\nconst redisCache = require('../redis');\n\nconst DEEPSEEK_URL = (process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com') + '/v1/chat/completions';\n\nfunction buildSystemPrompt(facts, timezone) {\n const factLines = facts.length\n ? facts.map(f => '- ' + f.key + ': ' + f.value).join('\\n')\n : '(none yet)';\n\n const today = new Date().toISOString().split('T')[0];\n\n return 'You are Jarvis, a sharp and efficient personal AI assistant running on Telegram.\\n' +\n 'Current timezone: ' + timezone + '. Today is ' + today + '.\\n\\n' +\n 'Personal facts about the user:\\n' + factLines + '\\n\\n' +\n 'AVAILABLE TOOLS:\\n' +\n '- create_reminder: set a time-based reminder\\n' +\n '- create_event: add a calendar event\\n' +\n '- add_note: save a note or idea\\n' +\n '- get_today: fetch today\\'s schedule and reminders\\n' +\n '- set_fact: store a personal preference or fact about the user\\n\\n' +\n 'STRICT OUTPUT FORMAT - respond with ONLY valid JSON, nothing else:\\n\\n' +\n '1. Regular reply:\\n' +\n '{\"type\":\"message\",\"content\":\"your reply here\"}\\n\\n' +\n '2. Tool call:\\n' +\n '{\"type\":\"tool\",\"name\":\"tool_name\",\"args\":{...}}\\n\\n' +\n 'For times use ISO-8601: YYYY-MM-DDTHH:mm:ss\\n' +\n 'Convert relative times like \"at 9pm\" or \"in 2 hours\" to absolute datetime.\\n\\n' +\n 'TOOL SCHEMAS:\\n' +\n 'create_reminder: { \"text\": string, \"time\": \"ISO-8601\" }\\n' +\n 'create_event: { \"title\": string, \"time\": \"ISO-8601\", \"duration_minutes\": number }\\n' +\n 'add_note: { \"content\": string }\\n' +\n 'get_today: {}\\n' +\n 'set_fact: { \"key\": string, \"value\": string }';\n}\n\nasync function chat(userId, userMessage, conversationHistory) {\n if (!conversationHistory) conversationHistory = [];\n\n // Try Redis cache first, fall back to DB\n let facts = await redisCache.getFactsCache(userId);\n if (facts === null) {\n facts = await db.getAllFacts(userId);\n // Populate cache for next time (fire-and-forget)\n redisCache.setFactsCache(userId, facts);\n }\n\n const systemPrompt = buildSystemPrompt(facts, process.env.TIMEZONE || 'UTC');\n\n const messages = [\n { role: 'system', content: systemPrompt }\n ].concat(conversationHistory).concat([\n { role: 'user', content: userMessage }\n ]);\n\n const response = await axios.post(\n DEEPSEEK_URL,\n {\n model: 'deepseek-chat',\n messages: messages,\n max_tokens: 500,\n temperature: 0.3,\n },\n {\n headers: {\n 'Authorization': 'Bearer ' + process.env.DEEPSEEK_API_KEY,\n 'Content-Type': 'application/json',\n },\n timeout: 15000,\n }\n );\n\n const rawText = response.data.choices[0].message.content.trim();\n\n try {\n const cleaned = rawText.replace(/^```(?:json)?\\n?/, '').replace(/\\n?```$/, '').trim();\n const parsed = JSON.parse(cleaned);\n if (parsed.type === 'message' || parsed.type === 'tool') {\n return parsed;\n }\n return { type: 'message', content: rawText };\n } catch (e) {\n return { type: 'message', content: rawText };\n }\n}\n\nmodule.exports = { chat };\n"}, "846b1100ee89ad4982ab69f48e38ad97037c6528": {"t": 1785766773.7782493, "n": 2920, "h": "83db41bc9694e1c2", "c": "// src/llm/deepseek.js\n// Handles all communication with DeepSeek API\nrequire('dotenv').config();\nconst axios = require('axios');\nconst db = require('../db');\n\nconst DEEPSEEK_URL = (process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com') + '/v1/chat/completions';\n\nfunction buildSystemPrompt(facts, timezone) {\n const factLines = facts.length\n ? facts.map(f => '- ' + f.key + ': ' + f.value).join('\\n')\n : '(none yet)';\n\n const today = new Date().toISOString().split('T')[0];\n\n return 'You are Jarvis, a sharp and efficient personal AI assistant running on Telegram.\\n' +\n 'Current timezone: ' + timezone + '. Today is ' + today + '.\\n\\n' +\n 'Personal facts about the user:\\n' + factLines + '\\n\\n' +\n 'AVAILABLE TOOLS:\\n' +\n '- create_reminder: set a time-based reminder\\n' +\n '- create_event: add a calendar event\\n' +\n '- add_note: save a note or idea\\n' +\n '- get_today: fetch today\\'s schedule and reminders\\n' +\n '- set_fact: store a personal preference or fact about the user\\n\\n' +\n 'STRICT OUTPUT FORMAT - respond with ONLY valid JSON, nothing else:\\n\\n' +\n '1. Regular reply:\\n' +\n '{\"type\":\"message\",\"content\":\"your reply here\"}\\n\\n' +\n '2. Tool call:\\n' +\n '{\"type\":\"tool\",\"name\":\"tool_name\",\"args\":{...}}\\n\\n' +\n 'For times use ISO-8601: YYYY-MM-DDTHH:mm:ss\\n' +\n 'Convert relative times like \"at 9pm\" or \"in 2 hours\" to absolute datetime.\\n\\n' +\n 'TOOL SCHEMAS:\\n' +\n 'create_reminder: { \"text\": string, \"time\": \"ISO-8601\" }\\n' +\n 'create_event: { \"title\": string, \"time\": \"ISO-8601\", \"duration_minutes\": number }\\n' +\n 'add_note: { \"content\": string }\\n' +\n 'get_today: {}\\n' +\n 'set_fact: { \"key\": string, \"value\": string }';\n}\n\nasync function chat(userId, userMessage, conversationHistory) {\n if (!conversationHistory) conversationHistory = [];\n\n const facts = await db.getAllFacts(userId);\n const systemPrompt = buildSystemPrompt(facts, process.env.TIMEZONE || 'UTC');\n\n const messages = [\n { role: 'system', content: systemPrompt }\n ].concat(conversationHistory).concat([\n { role: 'user', content: userMessage }\n ]);\n\n const response = await axios.post(\n DEEPSEEK_URL,\n {\n model: 'deepseek-chat',\n messages: messages,\n max_tokens: 500,\n temperature: 0.3,\n },\n {\n headers: {\n 'Authorization': 'Bearer ' + process.env.DEEPSEEK_API_KEY,\n 'Content-Type': 'application/json',\n },\n timeout: 15000,\n }\n );\n\n const rawText = response.data.choices[0].message.content.trim();\n\n try {\n const cleaned = rawText.replace(/^```(?:json)?\\n?/, '').replace(/\\n?```$/, '').trim();\n const parsed = JSON.parse(cleaned);\n if (parsed.type === 'message' || parsed.type === 'tool') {\n return parsed;\n }\n return { type: 'message', content: rawText };\n } catch (e) {\n return { type: 'message', content: rawText };\n }\n}\n\nmodule.exports = { chat };\n"}}}}