diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index b2d202f2e6..34987b9de6 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -2230,18 +2230,32 @@ export async function handleChatCore({ // tier-2) never fire from the chat hot path — they only fire in the // Playground (retrievePreview, which gets `query` as a positional arg). const lastUserQuery = ((): string => { + // Responses API item types that are NOT user input — never accept + // their text as the retrieval query (e.g. function_call_output is the + // tool's reply, reasoning is the model's chain of thought). + const NON_USER_TYPES = new Set([ + "function_call", + "function_call_output", + "tool_call", + "tool_call_output", + "reasoning", + "computer_call", + "computer_call_output", + "web_search_call", + "file_search_call", + ]); + function pickFrom(arr: unknown[]): string { for (let i = arr.length - 1; i >= 0; i--) { const item = arr[i] as Record | undefined; if (!item) continue; - // Chat API: role==="user"; Responses API: role on input items, or - // type==="input_text"; some clients omit role entirely on the - // first input item — accept those too as a last resort. - if ( - item.role !== undefined && - item.role !== "user" - ) { - continue; + // Chat API: only role==="user" items. Responses API items often + // have type instead of role — skip non-user types like + // function_call_output so the tool's reply doesn't leak into the + // memory query. + if (item.role !== undefined && item.role !== "user") continue; + if (item.role === undefined && typeof item.type === "string") { + if (NON_USER_TYPES.has(item.type)) continue; } const content = item.content ?? item.text; if (typeof content === "string" && content.trim().length > 0) { @@ -2254,6 +2268,16 @@ export async function handleChatCore({ parts.push(p); } else if (p && typeof p === "object") { const pp = p as Record; + // Skip non-text content parts (image_url, tool_use, etc.) + const ptype = typeof pp.type === "string" ? pp.type : ""; + if ( + ptype && + ptype !== "text" && + ptype !== "input_text" && + ptype !== "output_text" + ) { + continue; + } const t = pp.text ?? pp.input_text; if (typeof t === "string") parts.push(t); } diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 5b6b6c2d7b..e835d8b925 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -3044,7 +3044,8 @@ "qdrantDisabled": "Disabled", "qdrantOk": "Healthy ({latencyMs}ms)", "qdrantError": "Connection error", - "needsReindex": "{count} memory(ies) need reindexing" + "needsReindex": "{count} memory(ies) need reindexing", + "configureCta": "Configure" }, "episodic": "Episodic", "export": "Export", diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index b0400b230e..b1df0e2f2b 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -3041,7 +3041,8 @@ "qdrantDisabled": "Desabilitado", "qdrantOk": "Saudável ({latencyMs}ms)", "qdrantError": "Erro de conexão", - "needsReindex": "{count} memória(s) precisam de reindexação" + "needsReindex": "{count} memória(s) precisam de reindexação", + "configureCta": "Configurar" }, "episodic": "Episódica", "export": "Exportar",