diff --git a/gemini-api-server.js b/gemini-api-server.js index 581b940..01bcf2f 100644 --- a/gemini-api-server.js +++ b/gemini-api-server.js @@ -94,10 +94,11 @@ import { GeminiApiService, API_ACTIONS, formatExpiryTime, - logPrompt, + logConversation, // Changed from logPrompt extractPromptText, extractResponseText, - getRequestBody + getRequestBody, + manageSystemPrompt, } from './gemini-core.js'; // --- Configuration Parsing --- @@ -209,9 +210,13 @@ async function handleStreamRequest(res, service, model, requestBody) { const stream = service.generateContentStream(model, requestBody); console.log('[Server Response Stream]'); process.stdout.write('> '); + let fullResponseText = ''; for await (const chunk of stream) { const chunkText = extractResponseText(chunk); - if (chunkText) process.stdout.write(chunkText); + if (chunkText) { + process.stdout.write(chunkText); + fullResponseText += chunkText; + } const chunkString = JSON.stringify(chunk); res.write(`data: ${chunkString}\n\n`); } @@ -219,6 +224,8 @@ async function handleStreamRequest(res, service, model, requestBody) { res.end(); const expiryDate = service.authClient.credentials.expiry_date; console.log(`[Auth Token] Time until expiry: ${formatExpiryTime(expiryDate)}`); + + await logConversation('output', fullResponseText, PROMPT_LOG_MODE, PROMPT_LOG_FILENAME); } async function handleUnaryRequest(res, service, model, requestBody) { const response = await service.generateContent(model, requestBody); @@ -232,6 +239,8 @@ async function handleUnaryRequest(res, service, model, requestBody) { res.end(responseString); const expiryDate = service.authClient.credentials.expiry_date; console.log(`[Auth Token] Time until expiry: ${formatExpiryTime(expiryDate)}`); + + await logConversation('output', responseText, PROMPT_LOG_MODE, PROMPT_LOG_FILENAME); } function handleError(res, error) { console.error('\n[Server] Request failed:', error.stack); @@ -271,16 +280,16 @@ async function requestHandler(req, res) { const [, model, action] = urlMatch; const requestBody = await getRequestBody(req); - if (PROMPT_LOG_MODE !== 'none') { - const promptText = extractPromptText(requestBody); - await logPrompt(promptText, PROMPT_LOG_MODE, PROMPT_LOG_FILENAME); - } + await manageSystemPrompt(requestBody); // Call the new function here + const promptText = extractPromptText(requestBody); + await logConversation('input', promptText, PROMPT_LOG_MODE, PROMPT_LOG_FILENAME); if (action === API_ACTIONS.STREAM_GENERATE_CONTENT) { await handleStreamRequest(res, service, model, requestBody); } else { await handleUnaryRequest(res, service, model, requestBody); } + return; } diff --git a/gemini-core.js b/gemini-core.js index bddc159..e2e12c9 100644 --- a/gemini-core.js +++ b/gemini-core.js @@ -17,7 +17,7 @@ export const API_ACTIONS = { GENERATE_CONTENT: 'generateContent', STREAM_GENERATE_CONTENT: 'streamGenerateContent', }; - +const SYSTEM_PROMPT_FILE = path.join(process.cwd(), 'system_prompt.txt'); // --- Utility Functions --- export function ensureRolesInContents(requestBody) { @@ -54,41 +54,77 @@ export function formatExpiryTime(expiryTimestamp) { return `${pad(hours)}h ${pad(minutes)}m ${pad(seconds)}s`; } -export async function logPrompt(promptText, logMode, logFilename) { +export async function logConversation(type, content, logMode, logFilename) { if (logMode === 'none') return; - const logContent = `--- Prompt Log @ ${new Date().toISOString()} ---\n${promptText}\n--------------------------------------\n\n`; - if (logMode === 'console') { - console.log(logContent); + + const timestamp = new Date().toLocaleString(); + const logEntry = `${timestamp} [${type.toUpperCase()}]:\n${content}\n\n\n--------------------------------------\n\n\n`; + + if (logMode === 'console' && type === 'input') { + console.log(logEntry); } else if (logMode === 'file') { try { - await fs.appendFile(logFilename, logContent); + // Append to the file + await fs.appendFile(logFilename, logEntry); } catch (err) { - console.error(`[Error] Failed to write prompt to ${logFilename}:`, err); + console.error(`[Error] Failed to write conversation log to ${logFilename}:`, err); } } } export function extractPromptText(requestBody) { - if (!requestBody) return "[No request body found]"; - const logParts = []; - - // ** FIX: Check for both snake_case and camelCase for system instruction ** - const systemInstruction = requestBody.system_instruction || requestBody.systemInstruction; + if (!requestBody || !Array.isArray(requestBody.contents)) return "[No request body found]"; - if (systemInstruction && Array.isArray(systemInstruction.parts)) { - const systemText = systemInstruction.parts.filter(p => p && typeof p.text === 'string').map(p => p.text).join('\n'); - if (systemText) logParts.push(`[SYSTEM INSTRUCTION]\n${systemText}`); - } + let latestPrompt = "[No text prompt found]"; - if (Array.isArray(requestBody.contents)) { - const userText = requestBody.contents.flatMap(c => (c && Array.isArray(c.parts) ? c.parts : [])).filter(p => p && typeof p.text === 'string').map(p => p.text).join('\n'); - if (userText) { - if (logParts.length > 0) logParts.push('----------------------'); - logParts.push(`[USER PROMPT]\n${userText}`); + // Iterate through contents in reverse to find the latest user prompt + for (let i = requestBody.contents.length - 1; i >= 0; i--) { + const content = requestBody.contents[i]; + if (content && content.role === 'user' && Array.isArray(content.parts)) { + const userParts = content.parts.filter(part => part && typeof part.text === 'string'); + if (userParts.length > 0) { + latestPrompt = userParts.map(part => part.text).join('\n'); + break; // Found the latest user prompt, exit loop + } } } - if (logParts.length === 0) return "[No text prompt or system instruction found]"; - return logParts.join('\n'); + return latestPrompt; +} + + +export async function manageSystemPrompt(requestBody) { + const incomingSystemInstruction = requestBody.system_instruction || requestBody.systemInstruction; + let incomingSystemText = ''; + + if (incomingSystemInstruction && Array.isArray(incomingSystemInstruction.parts)) { + incomingSystemText = incomingSystemInstruction.parts + .filter(p => p && typeof p.text === 'string') + .map(p => p.text) + .join('\n'); + } + + try { + let currentSystemText = ''; + try { + currentSystemText = await fs.readFile(SYSTEM_PROMPT_FILE, 'utf8'); + } catch (error) { + if (error.code !== 'ENOENT') { + console.error(`[System Prompt Manager] Error reading system prompt file: ${error.message}`); + } + // If file doesn't exist, currentSystemText remains empty, which is fine. + } + + if (incomingSystemText && incomingSystemText !== currentSystemText) { + await fs.writeFile(SYSTEM_PROMPT_FILE, incomingSystemText); + console.log('[System Prompt Manager] System prompt updated in file.'); + } else if (!incomingSystemText && currentSystemText) { + // If incoming request has no system prompt but file has one, clear the file + await fs.writeFile(SYSTEM_PROMPT_FILE, ''); + console.log('[System Prompt Manager] System prompt cleared from file.'); + } + } catch (error) { + console.error(`[System Prompt Manager] Failed to manage system prompt file: ${error.message}`); + } } export function extractResponseText(responseObject) { diff --git a/openai-api-server.js b/openai-api-server.js index d3a7e95..c25ba9c 100644 --- a/openai-api-server.js +++ b/openai-api-server.js @@ -117,10 +117,11 @@ import { GeminiApiService, API_ACTIONS, formatExpiryTime, - logPrompt, + logConversation, // Changed from logPrompt extractPromptText, getRequestBody, - extractResponseText + extractResponseText, + manageSystemPrompt, // New import } from './gemini-core.js'; // --- Configuration Parsing --- @@ -424,12 +425,14 @@ async function handleStreamRequest(res, service, model, requestBody) { const stream = service.generateContentStream(model, requestBody); console.log('[Server Response Stream]'); process.stdout.write('> '); + let fullResponseText = ''; // Declare fullResponseText here try { for await (const chunk of stream) { const openAIChunk = toOpenAIStreamChunk(chunk, model); const chunkText = openAIChunk.choices[0].delta.content || ""; if (chunkText) { process.stdout.write(chunkText); + fullResponseText += chunkText; // Accumulate text here } res.write(`data: ${JSON.stringify(openAIChunk)}\n\n`); } @@ -447,6 +450,8 @@ async function handleStreamRequest(res, service, model, requestBody) { if (!res.writableEnded) { res.end(); } + // Log the full conversation here + await logConversation('output', fullResponseText, PROMPT_LOG_MODE, PROMPT_LOG_FILENAME); } const expiryDate = service.authClient.credentials.expiry_date; console.log(`[Auth Token] Time until expiry: ${formatExpiryTime(expiryDate)}`); @@ -465,6 +470,8 @@ async function handleUnaryRequest(res, service, model, requestBody) { res.end(JSON.stringify(openAIResponse)); const expiryDate = service.authClient.credentials.expiry_date; console.log(`[Auth Token] Time until expiry: ${formatExpiryTime(expiryDate)}`); + + await logConversation('output', responseText, PROMPT_LOG_MODE, PROMPT_LOG_FILENAME); } function handleError(res, error) { @@ -504,10 +511,9 @@ async function requestHandler(req, res) { const model = openaiRequest.model; const geminiRequest = toGeminiRequest(openaiRequest); - if (PROMPT_LOG_MODE !== 'none') { - const promptText = extractPromptText(geminiRequest); // Use geminiRequest for logging - await logPrompt(promptText, PROMPT_LOG_MODE, PROMPT_LOG_FILENAME); - } + await manageSystemPrompt(geminiRequest); // Call the new function here + const promptText = extractPromptText(geminiRequest); // Use geminiRequest for logging + await logConversation('input', promptText, PROMPT_LOG_MODE, PROMPT_LOG_FILENAME); if (openaiRequest.stream) { await handleStreamRequest(res, service, model, geminiRequest);