Fix Atlas init: add user turn so Gemini doesn't reject empty conversation

When is_init=true, no user message was being added to history before
calling the LLM. Gemini requires at least one user turn — without it
the API returned "contents are required" and Atlas never sent its
opening greeting. Now adds the init message marked internally so it's
sent to the LLM but filtered out of returned/stored history.

Made-with: Cursor
This commit is contained in:
2026-03-17 15:56:50 -07:00
parent 8ae640c911
commit 772f5357a8

View File

@@ -88,14 +88,23 @@ export async function atlasChat(
const oaiTools = toOAITools(ATLAS_TOOLS); const oaiTools = toOAITools(ATLAS_TOOLS);
const systemPrompt = resolvePrompt('atlas'); const systemPrompt = resolvePrompt('atlas');
// For init triggers, don't add the synthetic prompt as a user turn // Always push the user message so Gemini gets a valid conversation (requires at least one user turn).
if (!opts?.isInit) { // For init triggers, we mark it so we can strip it from the returned history — it's an internal
session.history.push({ role: 'user', content: userMessage }); // prompt, not a real user message, and shouldn't appear in the conversation UI or DB.
} const INIT_MARKER = '__atlas_init_marker__';
session.history.push({
role: 'user',
content: opts?.isInit ? INIT_MARKER + userMessage : userMessage
});
const buildMessages = (): LLMMessage[] => [ const buildMessages = (): LLMMessage[] => [
{ role: 'system', content: systemPrompt }, { role: 'system', content: systemPrompt },
...session.history.slice(-60) ...session.history.slice(-60).map(m =>
// Strip the init marker before sending to the LLM
m.role === 'user' && typeof m.content === 'string' && m.content.startsWith(INIT_MARKER)
? { ...m, content: m.content.slice(INIT_MARKER.length) }
: m
)
]; ];
let turn = 0; let turn = 0;
@@ -156,6 +165,8 @@ export async function atlasChat(
reply: finalReply, reply: finalReply,
sessionId, sessionId,
history: session.history history: session.history
// Drop the internal init user turn — it's not a real user message
.filter(m => !(m.role === 'user' && typeof m.content === 'string' && m.content.startsWith(INIT_MARKER)))
.filter(m => m.role !== 'assistant' || m.content || m.tool_calls?.length) .filter(m => m.role !== 'assistant' || m.content || m.tool_calls?.length)
.slice(-60), .slice(-60),
prdContent, prdContent,