mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-07-07 14:12:40 +08:00
feat: support attached images for all providers
This commit is contained in:
@@ -2,6 +2,7 @@ import crypto from 'node:crypto';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
import { parseImagesInputTag } from '@/shared/image-attachments.js';
|
||||
import type { IProviderSessions } from '@/shared/interfaces.js';
|
||||
import type { AnyRecord, FetchHistoryOptions, FetchHistoryResult, NormalizedMessage } from '@/shared/types.js';
|
||||
import {
|
||||
@@ -24,7 +25,7 @@ type CursorJsonBlob = CursorDbBlob & {
|
||||
parsed: AnyRecord;
|
||||
};
|
||||
|
||||
type CursorMessageBlob = {
|
||||
export type CursorMessageBlob = {
|
||||
id: string;
|
||||
sequence: number;
|
||||
rowid: number;
|
||||
@@ -76,6 +77,27 @@ function unwrapUserQueryText(value: string, role: 'user' | 'assistant'): string
|
||||
return value.replace(/^\s*<timestamp>[\s\S]*?<\/timestamp>\s*/, '').trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwraps one user-authored text payload and splits off the `<images_input>`
|
||||
* attachment block appended by the chat composer. Assistant text passes
|
||||
* through untouched.
|
||||
*/
|
||||
function extractUserTextAndImages(
|
||||
value: string,
|
||||
role: 'user' | 'assistant',
|
||||
): { text: string; images?: Array<{ path: string; name?: string }> } {
|
||||
const unwrapped = unwrapUserQueryText(value, role);
|
||||
if (role !== 'user') {
|
||||
return { text: unwrapped };
|
||||
}
|
||||
|
||||
const { text, attachments } = parseImagesInputTag(unwrapped);
|
||||
return {
|
||||
text,
|
||||
images: attachments.length > 0 ? attachments : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeToolId(value: unknown): string | null {
|
||||
if (typeof value !== 'string') {
|
||||
return null;
|
||||
@@ -405,8 +427,11 @@ export class CursorSessionsProvider implements IProviderSessions {
|
||||
/**
|
||||
* Converts Cursor SQLite message blobs into normalized messages and attaches
|
||||
* matching tool results to their tool_use entries.
|
||||
*
|
||||
* Public so tests can drive history normalization with synthetic blobs
|
||||
* without needing a real Cursor store.db.
|
||||
*/
|
||||
private normalizeCursorBlobs(blobs: CursorMessageBlob[], sessionId: string | null): NormalizedMessage[] {
|
||||
normalizeCursorBlobs(blobs: CursorMessageBlob[], sessionId: string | null): NormalizedMessage[] {
|
||||
const messages: NormalizedMessage[] = [];
|
||||
const toolUseMap = new Map<string, NormalizedMessage>();
|
||||
const baseTime = Date.now();
|
||||
@@ -446,7 +471,16 @@ export class CursorSessionsProvider implements IProviderSessions {
|
||||
text = unwrapUserQueryText(content.message.content, role);
|
||||
}
|
||||
}
|
||||
if (text?.trim()) {
|
||||
const { text: cleanText, images } = role === 'user'
|
||||
? (() => {
|
||||
const parsed = parseImagesInputTag(text);
|
||||
return {
|
||||
text: parsed.text,
|
||||
images: parsed.attachments.length > 0 ? parsed.attachments : undefined,
|
||||
};
|
||||
})()
|
||||
: { text, images: undefined };
|
||||
if (cleanText?.trim() || images) {
|
||||
messages.push(createNormalizedMessage({
|
||||
id: baseId,
|
||||
sessionId,
|
||||
@@ -454,7 +488,8 @@ export class CursorSessionsProvider implements IProviderSessions {
|
||||
provider: PROVIDER,
|
||||
kind: 'text',
|
||||
role,
|
||||
content: text,
|
||||
content: cleanText,
|
||||
images,
|
||||
sequence: blob.sequence,
|
||||
rowid: blob.rowid,
|
||||
}));
|
||||
@@ -506,8 +541,8 @@ export class CursorSessionsProvider implements IProviderSessions {
|
||||
}
|
||||
|
||||
if (part?.type === 'text' && part?.text) {
|
||||
const normalizedPartText = unwrapUserQueryText(part.text, role);
|
||||
if (!normalizedPartText) {
|
||||
const { text: normalizedPartText, images } = extractUserTextAndImages(part.text, role);
|
||||
if (!normalizedPartText && !images) {
|
||||
continue;
|
||||
}
|
||||
messages.push(createNormalizedMessage({
|
||||
@@ -518,6 +553,7 @@ export class CursorSessionsProvider implements IProviderSessions {
|
||||
kind: 'text',
|
||||
role,
|
||||
content: normalizedPartText,
|
||||
images,
|
||||
sequence: blob.sequence,
|
||||
rowid: blob.rowid,
|
||||
}));
|
||||
@@ -557,8 +593,8 @@ export class CursorSessionsProvider implements IProviderSessions {
|
||||
&& content.content.trim()
|
||||
&& !isInternalCursorText(content.content)
|
||||
) {
|
||||
const normalizedText = unwrapUserQueryText(content.content, role);
|
||||
if (!normalizedText) {
|
||||
const { text: normalizedText, images } = extractUserTextAndImages(content.content, role);
|
||||
if (!normalizedText && !images) {
|
||||
continue;
|
||||
}
|
||||
messages.push(createNormalizedMessage({
|
||||
@@ -569,6 +605,7 @@ export class CursorSessionsProvider implements IProviderSessions {
|
||||
kind: 'text',
|
||||
role,
|
||||
content: normalizedText,
|
||||
images,
|
||||
sequence: blob.sequence,
|
||||
rowid: blob.rowid,
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user