fix(wechat): scrub image_url from sessions and harden vision handoff.
Memind CI / Test, build, and release guards (pull_request) Failing after 19s
Memind CI / Test, build, and release guards (pull_request) Failing after 19s
Strip image_url from persisted session payloads, keep image turns scoped to the active request, and align proxy/vision/page-data paths so WeChat image history does not leak into fresh sessions. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+53
-7
@@ -34,6 +34,7 @@ import {
|
||||
import { extractAttachmentText } from './mindspace-attachment-text.mjs';
|
||||
import {
|
||||
buildCurrentTurnImageScopeNote,
|
||||
conversationHasImageUrlContent,
|
||||
extractCurrentTurnImageUrls,
|
||||
scrubConversationHistoricalImageAttachments,
|
||||
} from './chat-image-turn-scope.mjs';
|
||||
@@ -974,6 +975,9 @@ export async function buildVisionPayload({
|
||||
'不要向用户展示 HTML 代码块。';
|
||||
|
||||
let updatedContent = Array.isArray(userMessage?.content) ? [...userMessage.content] : [];
|
||||
// Text-only Goose providers cannot accept image_url parts. After VL analysis,
|
||||
// keep only text (with the injected vision note) for the agent turn.
|
||||
updatedContent = updatedContent.filter((item) => item?.type !== 'image_url');
|
||||
for (const item of imageItems) {
|
||||
updatedContent = updatedContent.map((c) => {
|
||||
if (c?.type !== 'text' || typeof c.text !== 'string') return c;
|
||||
@@ -1698,27 +1702,59 @@ export function createTkmindProxy({
|
||||
return { changed: false, updated: false, status: upstream.status };
|
||||
}
|
||||
const session = await upstream.json().catch(() => null);
|
||||
const { conversation, changed } = scrubConversationHistoricalImageAttachments(
|
||||
session?.conversation ?? [],
|
||||
const conversation = Array.isArray(session?.conversation) ? session.conversation : [];
|
||||
const hasImageUrlContent = conversationHasImageUrlContent(conversation, {
|
||||
excludeMessageId: activeId,
|
||||
});
|
||||
const { conversation: scrubbedConversation, changed } = scrubConversationHistoricalImageAttachments(
|
||||
conversation,
|
||||
activeId,
|
||||
);
|
||||
if (!changed) return { changed: false, updated: false };
|
||||
// Text-only providers (DeepSeek) reject any lingering image_url parts. If Goose
|
||||
// cannot persist a scrub (405/404), callers must rotate to a fresh session.
|
||||
if (!changed && !hasImageUrlContent) {
|
||||
return { changed: false, updated: false, hasImageUrlContent: false };
|
||||
}
|
||||
if (!changed && hasImageUrlContent) {
|
||||
return {
|
||||
changed: true,
|
||||
updated: false,
|
||||
status: 'image_url_content_present',
|
||||
hasImageUrlContent: true,
|
||||
};
|
||||
}
|
||||
const update = await apiFetch(
|
||||
target,
|
||||
apiSecret,
|
||||
`/sessions/${encodeURIComponent(sessionId)}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ conversation }),
|
||||
body: JSON.stringify({ conversation: scrubbedConversation }),
|
||||
},
|
||||
);
|
||||
if (!update.ok) {
|
||||
console.warn(
|
||||
`Historical image scrub skipped for session ${sessionId}: upstream ${update.status}`,
|
||||
);
|
||||
return { changed: true, updated: false, status: update.status };
|
||||
return {
|
||||
changed: true,
|
||||
updated: false,
|
||||
status: update.status,
|
||||
hasImageUrlContent:
|
||||
hasImageUrlContent
|
||||
|| conversationHasImageUrlContent(scrubbedConversation, {
|
||||
excludeMessageId: activeId,
|
||||
}),
|
||||
};
|
||||
}
|
||||
return { changed: true, updated: true, status: update.status };
|
||||
return {
|
||||
changed: true,
|
||||
updated: true,
|
||||
status: update.status,
|
||||
hasImageUrlContent: conversationHasImageUrlContent(scrubbedConversation, {
|
||||
excludeMessageId: activeId,
|
||||
}),
|
||||
};
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
'Historical image scrub skipped:',
|
||||
@@ -1938,7 +1974,17 @@ export function createTkmindProxy({
|
||||
if (!user) throw new Error('用户不存在');
|
||||
if (requireHistoricalImageIsolation || messageHasImages(userMessage)) {
|
||||
const imageIsolation = await syncHistoricalImageTurnIsolation(sessionId, userMessage?.id);
|
||||
if (requireHistoricalImageIsolation && imageIsolation.changed && !imageIsolation.updated) {
|
||||
const scrubUnsupported =
|
||||
imageIsolation.changed
|
||||
&& !imageIsolation.updated
|
||||
&& (
|
||||
requireHistoricalImageIsolation
|
||||
|| imageIsolation.hasImageUrlContent
|
||||
|| Number(imageIsolation.status) === 404
|
||||
|| Number(imageIsolation.status) === 405
|
||||
|| imageIsolation.status === 'image_url_content_present'
|
||||
);
|
||||
if (scrubUnsupported) {
|
||||
const error = new Error(
|
||||
`historical_image_session_update_unsupported:${imageIsolation.status ?? 'unknown'}`,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user