fix: finish review-driven single-source cleanup

This commit is contained in:
Cita
2026-03-23 11:26:01 +08:00
parent 29c9fcbd29
commit 328b492ab3
21 changed files with 926 additions and 320 deletions
+10 -4
View File
@@ -17,6 +17,12 @@ function normalizeValue(value) {
return (value || '').trim().toLowerCase();
}
function normalizeMimeType(value) {
const normalized = normalizeValue(value);
const [essence] = normalized.split(';');
return (essence || '').trim();
}
export function inferConversationFileMimeType(filename) {
const normalized = normalizeValue(filename);
if (normalized.endsWith('.pdf')) return 'application/pdf';
@@ -41,7 +47,7 @@ export function inferConversationFileMimeType(filename) {
}
export function resolveConversationFileMimeType(mimeType, filename) {
const normalizedMimeType = normalizeValue(mimeType);
const normalizedMimeType = normalizeMimeType(mimeType);
if (normalizedMimeType && !GENERIC_MIME_TYPES.has(normalizedMimeType)) {
return normalizedMimeType;
}
@@ -49,14 +55,14 @@ export function resolveConversationFileMimeType(mimeType, filename) {
}
export function classifyConversationFileMimeType(mimeType) {
const normalized = normalizeValue(mimeType);
const normalized = normalizeMimeType(mimeType);
if (normalized.startsWith('image/')) return 'image';
if (normalized.startsWith('audio/')) return 'audio';
return 'document';
}
export function detectConversationFileKind(file) {
const mimeType = normalizeValue(file?.mimeType);
const mimeType = normalizeMimeType(file?.mimeType);
if (mimeType) {
if (mimeType.startsWith('image/')) return 'image';
if (mimeType.startsWith('audio/')) return 'audio';
@@ -73,7 +79,7 @@ export function detectConversationFileKind(file) {
}
export function isSupportedConversationFileMimeType(mimeType) {
const normalized = normalizeValue(mimeType);
const normalized = normalizeMimeType(mimeType);
return DOCUMENT_MIME_TYPES.has(normalized)
|| normalized.startsWith('image/')
|| normalized.startsWith('audio/');
+10
View File
@@ -42,4 +42,14 @@ describe('conversationFileTypes', () => {
mimeType: 'application/octet-stream',
})).toBe('document');
});
it('strips MIME parameters before matching support and kind rules', () => {
expect(resolveConversationFileMimeType('text/plain; charset=utf-8', 'notes.txt')).toBe('text/plain');
expect(classifyConversationFileMimeType('text/plain; charset=utf-8')).toBe('document');
expect(detectConversationFileKind({
filename: 'notes.txt',
mimeType: 'text/plain; charset=utf-8',
})).toBe('document');
expect(isSupportedConversationFileMimeType('text/plain; charset=utf-8')).toBe(true);
});
});
+6 -4
View File
@@ -86,6 +86,11 @@ function isSafeRegexPatternBody(body) {
continue;
}
if (ch === '\\') {
const next = body[index + 1];
if (!next) return false;
if (/[a-z]/i.test(next) && next !== 'd') {
return false;
}
escaped = true;
continue;
}
@@ -99,10 +104,7 @@ function isSafeRegexPatternBody(body) {
}
if (ch === '(') {
if (body[index + 1] === '?') {
if (body[index + 2] !== ':') return false;
groupStack.push({ hasInnerQuantifier: false, hasAlternation: false });
index += 2;
continue;
return false;
}
groupStack.push({ hasInnerQuantifier: false, hasAlternation: false });
continue;
+12
View File
@@ -21,6 +21,18 @@ describe('token route pattern helpers', () => {
expect(matchesTokenRouteModelPattern('claude-sonnet-4-6', 're:(?=claude)')).toBe(false);
});
it('rejects regex syntax that the lightweight parser does not implement', async () => {
const {
matchesTokenRouteModelPattern,
parseTokenRouteRegexPattern,
} = await import('./tokenRoutePatterns.js');
expect(parseTokenRouteRegexPattern('re:^(?:gpt|claude)-5$').regex).toBeNull();
expect(matchesTokenRouteModelPattern('gpt-5', 're:^(?:gpt|claude)-5$')).toBe(false);
expect(parseTokenRouteRegexPattern('re:^gpt-\\s+$').regex).toBeNull();
expect(matchesTokenRouteModelPattern('gpt- ', 're:^gpt-\\s+$')).toBe(false);
});
it('supports exact, glob, and safe regex route matches', async () => {
const { matchesTokenRouteModelPattern } = await import('./tokenRoutePatterns.js');