forked from boos/BoosAPI
fix: address review feedback and code scanning alerts
This commit is contained in:
@@ -77,6 +77,16 @@ export async function convergeAccountMutation(input: {
|
||||
result.tokenSync = tokenSync;
|
||||
result.defaultTokenId = tokenSync.defaultTokenId ?? result.defaultTokenId;
|
||||
}
|
||||
if (!input.ensurePreferredTokenBeforeSync && input.preferredApiToken?.trim()) {
|
||||
const defaultTokenId = await runStep(() => ensureDefaultTokenForAccount(
|
||||
input.accountId,
|
||||
input.preferredApiToken!,
|
||||
{ name: 'default', source: input.defaultTokenSource || 'manual' },
|
||||
));
|
||||
if (defaultTokenId != null) {
|
||||
result.defaultTokenId = defaultTokenId;
|
||||
}
|
||||
}
|
||||
} else if (!input.ensurePreferredTokenBeforeSync && input.preferredApiToken?.trim()) {
|
||||
const defaultTokenId = await runStep(() => ensureDefaultTokenForAccount(
|
||||
input.accountId,
|
||||
|
||||
@@ -617,7 +617,11 @@ function normalizeOriginUrl(raw: string): string {
|
||||
function resolveImportedPlatform(rawPlatform: unknown, rawUrl: string): string {
|
||||
const rawPlatformText = asString(rawPlatform).toLowerCase();
|
||||
const normalizedPlatform = rawPlatformText
|
||||
? (PLATFORM_ALIASES[rawPlatformText] ?? (DIRECT_API_PLATFORMS.has(rawPlatformText) ? rawPlatformText : ''))
|
||||
? (
|
||||
Object.prototype.hasOwnProperty.call(PLATFORM_ALIASES, rawPlatformText)
|
||||
? PLATFORM_ALIASES[rawPlatformText]
|
||||
: (DIRECT_API_PLATFORMS.has(rawPlatformText) ? rawPlatformText : '')
|
||||
)
|
||||
: '';
|
||||
if (normalizedPlatform) return normalizedPlatform;
|
||||
|
||||
|
||||
@@ -516,15 +516,16 @@ export async function listOauthConnections(options: {
|
||||
routeChannelCountByAccount.set(row.accountId, row.count ?? 0);
|
||||
}
|
||||
|
||||
const items = rows.map((row) => {
|
||||
const oauth = getOauthInfoFromAccount(row.accounts)!;
|
||||
const items = rows.flatMap((row) => {
|
||||
const oauth = getOauthInfoFromAccount(row.accounts);
|
||||
if (!oauth) return [];
|
||||
const models = modelMap.get(row.accounts.id) || [];
|
||||
const status = (
|
||||
oauth.modelDiscoveryStatus === 'abnormal'
|
||||
|| row.accounts.status !== 'active'
|
||||
|| row.sites.status !== 'active'
|
||||
) ? 'abnormal' : 'healthy';
|
||||
return {
|
||||
return [{
|
||||
accountId: row.accounts.id,
|
||||
siteId: row.sites.id,
|
||||
provider: oauth.provider,
|
||||
@@ -546,7 +547,7 @@ export async function listOauthConnections(options: {
|
||||
url: row.sites.url,
|
||||
platform: row.sites.platform,
|
||||
},
|
||||
};
|
||||
}];
|
||||
});
|
||||
|
||||
return { items, total, limit, offset };
|
||||
|
||||
@@ -13,7 +13,11 @@ type FetchModelsOptions = {
|
||||
};
|
||||
|
||||
export function normalizePlatformBaseUrl(baseUrl: string): string {
|
||||
return (baseUrl || '').replace(/\/+$/, '');
|
||||
let normalized = baseUrl || '';
|
||||
while (normalized.endsWith('/')) {
|
||||
normalized = normalized.slice(0, -1);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export function resolveVersionedModelsUrl(baseUrl: string): string {
|
||||
|
||||
@@ -123,4 +123,46 @@ describe('proxyInputFileResolver', () => {
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('falls back from application/octet-stream to filename-based mime inference', async () => {
|
||||
const { inlineLocalInputFileReferences } = await import('./proxyInputFileResolver.js');
|
||||
await expect(inlineLocalInputFileReferences(
|
||||
{
|
||||
model: 'gpt-5',
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: [
|
||||
{
|
||||
type: 'file',
|
||||
file: {
|
||||
filename: 'paper.pdf',
|
||||
mime_type: 'application/octet-stream',
|
||||
file_data: Buffer.from('%PDF-octet').toString('base64'),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{ ownerType: 'managed_key', ownerId: '7' },
|
||||
)).resolves.toEqual({
|
||||
model: 'gpt-5',
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: [
|
||||
{
|
||||
type: 'file',
|
||||
file: {
|
||||
file_data: Buffer.from('%PDF-octet').toString('base64'),
|
||||
filename: 'paper.pdf',
|
||||
mime_type: 'application/pdf',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { ProxyResourceOwner } from '../middleware/auth.js';
|
||||
import { getProxyFileByPublicIdForOwner } from './proxyFileStore.js';
|
||||
import { getProxyFileByPublicIdForOwner, LOCAL_PROXY_FILE_ID_PREFIX } from './proxyFileStore.js';
|
||||
import { ensureBase64DataUrl } from '../transformers/shared/inputFile.js';
|
||||
import { summarizeConversationFileInputsInOpenAiBody } from '../proxy-core/capabilities/conversationFileCapabilities.js';
|
||||
import { isSupportedConversationFileMimeType } from '../../shared/conversationFileTypes.js';
|
||||
|
||||
const LOCAL_PROXY_FILE_ID_PREFIX = 'file-metapi-';
|
||||
import {
|
||||
isSupportedConversationFileMimeType,
|
||||
resolveConversationFileMimeType,
|
||||
} from '../../shared/conversationFileTypes.js';
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return !!value && typeof value === 'object' && !Array.isArray(value);
|
||||
@@ -26,24 +27,6 @@ function cloneJsonValue<T>(value: T): T {
|
||||
return value;
|
||||
}
|
||||
|
||||
function inferMimeTypeFromFilename(filename: string): string {
|
||||
const normalized = filename.toLowerCase();
|
||||
if (normalized.endsWith('.pdf')) return 'application/pdf';
|
||||
if (normalized.endsWith('.txt')) return 'text/plain';
|
||||
if (normalized.endsWith('.md') || normalized.endsWith('.markdown')) return 'text/markdown';
|
||||
if (normalized.endsWith('.json')) return 'application/json';
|
||||
if (normalized.endsWith('.png')) return 'image/png';
|
||||
if (normalized.endsWith('.jpg') || normalized.endsWith('.jpeg')) return 'image/jpeg';
|
||||
if (normalized.endsWith('.gif')) return 'image/gif';
|
||||
if (normalized.endsWith('.webp')) return 'image/webp';
|
||||
if (normalized.endsWith('.wav')) return 'audio/wav';
|
||||
if (normalized.endsWith('.mp3')) return 'audio/mpeg';
|
||||
if (normalized.endsWith('.m4a')) return 'audio/mp4';
|
||||
if (normalized.endsWith('.ogg')) return 'audio/ogg';
|
||||
if (normalized.endsWith('.webm')) return 'audio/webm';
|
||||
return 'application/octet-stream';
|
||||
}
|
||||
|
||||
function isSupportedMimeType(mimeType: string): boolean {
|
||||
return isSupportedConversationFileMimeType(mimeType);
|
||||
}
|
||||
@@ -116,7 +99,7 @@ async function resolveInputFileLike(fileLike: InputFileLike, owner: ProxyResourc
|
||||
}> {
|
||||
if (fileLike.fileData) {
|
||||
const filename = fileLike.filename || 'upload.bin';
|
||||
const mimeType = fileLike.mimeType || inferMimeTypeFromFilename(filename);
|
||||
const mimeType = resolveConversationFileMimeType(fileLike.mimeType, filename);
|
||||
if (!isSupportedMimeType(mimeType)) {
|
||||
throw new ProxyInputFileResolutionError(400, `unsupported file mime type: ${mimeType}`);
|
||||
}
|
||||
@@ -133,7 +116,10 @@ async function resolveInputFileLike(fileLike: InputFileLike, owner: ProxyResourc
|
||||
if (!stored) {
|
||||
throw new ProxyInputFileResolutionError(404, `file not found: ${fileLike.fileId}`, 'not_found_error');
|
||||
}
|
||||
const mimeType = fileLike.mimeType || stored.mimeType || inferMimeTypeFromFilename(stored.filename);
|
||||
const mimeType = resolveConversationFileMimeType(
|
||||
fileLike.mimeType || stored.mimeType,
|
||||
stored.filename,
|
||||
);
|
||||
if (!isSupportedMimeType(mimeType)) {
|
||||
throw new ProxyInputFileResolutionError(400, `unsupported file mime type: ${mimeType}`);
|
||||
}
|
||||
@@ -145,7 +131,7 @@ async function resolveInputFileLike(fileLike: InputFileLike, owner: ProxyResourc
|
||||
}
|
||||
|
||||
const filename = fileLike.filename || 'upload.bin';
|
||||
const mimeType = fileLike.mimeType || inferMimeTypeFromFilename(filename);
|
||||
const mimeType = resolveConversationFileMimeType(fileLike.mimeType, filename);
|
||||
if (!fileLike.fileData) {
|
||||
throw new ProxyInputFileResolutionError(400, `file_data is required for inline file block: ${filename}`);
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ type Candidate = {
|
||||
|
||||
describe('filterRecentlyFailedCandidates', () => {
|
||||
it('rejects unsafe nested-quantifier regex route patterns', () => {
|
||||
expect(parseRegexModelPattern('re:^(a+)+$')).toBeNull();
|
||||
expect(matchesModelPattern('aaaa', 're:^(a+)+$')).toBe(false);
|
||||
expect(parseRegexModelPattern('re:(?=claude)')).toBeNull();
|
||||
expect(matchesModelPattern('claude-sonnet-4-6', 're:(?=claude)')).toBe(false);
|
||||
});
|
||||
|
||||
it('uses a short default recent-failure window', () => {
|
||||
|
||||
@@ -894,7 +894,7 @@ export function isRegexModelPattern(pattern: string): boolean {
|
||||
return isTokenRouteRegexPattern(pattern);
|
||||
}
|
||||
|
||||
export function parseRegexModelPattern(pattern: string): RegExp | null {
|
||||
export function parseRegexModelPattern(pattern: string): { test(value: string): boolean } | null {
|
||||
return parseTokenRouteRegexPattern(pattern).regex;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user